net-imap 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of net-imap might be problematic. Click here for more details.

@@ -0,0 +1,1530 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Net
4
+ class IMAP < Protocol
5
+
6
+ class ResponseParser # :nodoc:
7
+ def initialize
8
+ @str = nil
9
+ @pos = nil
10
+ @lex_state = nil
11
+ @token = nil
12
+ @flag_symbols = {}
13
+ end
14
+
15
+ def parse(str)
16
+ @str = str
17
+ @pos = 0
18
+ @lex_state = EXPR_BEG
19
+ @token = nil
20
+ return response
21
+ end
22
+
23
+ private
24
+
25
+ EXPR_BEG = :EXPR_BEG
26
+ EXPR_DATA = :EXPR_DATA
27
+ EXPR_TEXT = :EXPR_TEXT
28
+ EXPR_RTEXT = :EXPR_RTEXT
29
+ EXPR_CTEXT = :EXPR_CTEXT
30
+
31
+ T_SPACE = :SPACE
32
+ T_NIL = :NIL
33
+ T_NUMBER = :NUMBER
34
+ T_ATOM = :ATOM
35
+ T_QUOTED = :QUOTED
36
+ T_LPAR = :LPAR
37
+ T_RPAR = :RPAR
38
+ T_BSLASH = :BSLASH
39
+ T_STAR = :STAR
40
+ T_LBRA = :LBRA
41
+ T_RBRA = :RBRA
42
+ T_LITERAL = :LITERAL
43
+ T_PLUS = :PLUS
44
+ T_PERCENT = :PERCENT
45
+ T_CRLF = :CRLF
46
+ T_EOF = :EOF
47
+ T_TEXT = :TEXT
48
+
49
+ BEG_REGEXP = /\G(?:\
50
+ (?# 1: SPACE )( +)|\
51
+ (?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
52
+ (?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
53
+ (?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
54
+ (?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
55
+ (?# 6: LPAR )(\()|\
56
+ (?# 7: RPAR )(\))|\
57
+ (?# 8: BSLASH )(\\)|\
58
+ (?# 9: STAR )(\*)|\
59
+ (?# 10: LBRA )(\[)|\
60
+ (?# 11: RBRA )(\])|\
61
+ (?# 12: LITERAL )\{(\d+)\}\r\n|\
62
+ (?# 13: PLUS )(\+)|\
63
+ (?# 14: PERCENT )(%)|\
64
+ (?# 15: CRLF )(\r\n)|\
65
+ (?# 16: EOF )(\z))/ni
66
+
67
+ DATA_REGEXP = /\G(?:\
68
+ (?# 1: SPACE )( )|\
69
+ (?# 2: NIL )(NIL)|\
70
+ (?# 3: NUMBER )(\d+)|\
71
+ (?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
72
+ (?# 5: LITERAL )\{(\d+)\}\r\n|\
73
+ (?# 6: LPAR )(\()|\
74
+ (?# 7: RPAR )(\)))/ni
75
+
76
+ TEXT_REGEXP = /\G(?:\
77
+ (?# 1: TEXT )([^\x00\r\n]*))/ni
78
+
79
+ RTEXT_REGEXP = /\G(?:\
80
+ (?# 1: LBRA )(\[)|\
81
+ (?# 2: TEXT )([^\x00\r\n]*))/ni
82
+
83
+ CTEXT_REGEXP = /\G(?:\
84
+ (?# 1: TEXT )([^\x00\r\n\]]*))/ni
85
+
86
+ Token = Struct.new(:symbol, :value)
87
+
88
+ def response
89
+ token = lookahead
90
+ case token.symbol
91
+ when T_PLUS
92
+ result = continue_req
93
+ when T_STAR
94
+ result = response_untagged
95
+ else
96
+ result = response_tagged
97
+ end
98
+ while lookahead.symbol == T_SPACE
99
+ # Ignore trailing space for Microsoft Exchange Server
100
+ shift_token
101
+ end
102
+ match(T_CRLF)
103
+ match(T_EOF)
104
+ return result
105
+ end
106
+
107
+ def continue_req
108
+ match(T_PLUS)
109
+ token = lookahead
110
+ if token.symbol == T_SPACE
111
+ shift_token
112
+ return ContinuationRequest.new(resp_text, @str)
113
+ else
114
+ return ContinuationRequest.new(ResponseText.new(nil, ""), @str)
115
+ end
116
+ end
117
+
118
+ def response_untagged
119
+ match(T_STAR)
120
+ match(T_SPACE)
121
+ token = lookahead
122
+ if token.symbol == T_NUMBER
123
+ return numeric_response
124
+ elsif token.symbol == T_ATOM
125
+ case token.value
126
+ when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
127
+ return response_cond
128
+ when /\A(?:FLAGS)\z/ni
129
+ return flags_response
130
+ when /\A(?:ID)\z/ni
131
+ return id_response
132
+ when /\A(?:LIST|LSUB|XLIST)\z/ni
133
+ return list_response
134
+ when /\A(?:NAMESPACE)\z/ni
135
+ return namespace_response
136
+ when /\A(?:QUOTA)\z/ni
137
+ return getquota_response
138
+ when /\A(?:QUOTAROOT)\z/ni
139
+ return getquotaroot_response
140
+ when /\A(?:ACL)\z/ni
141
+ return getacl_response
142
+ when /\A(?:SEARCH|SORT)\z/ni
143
+ return search_response
144
+ when /\A(?:THREAD)\z/ni
145
+ return thread_response
146
+ when /\A(?:STATUS)\z/ni
147
+ return status_response
148
+ when /\A(?:CAPABILITY)\z/ni
149
+ return capability_response
150
+ when /\A(?:NOOP)\z/ni
151
+ return ignored_response
152
+ else
153
+ return text_response
154
+ end
155
+ else
156
+ parse_error("unexpected token %s", token.symbol)
157
+ end
158
+ end
159
+
160
+ def response_tagged
161
+ tag = astring_chars
162
+ match(T_SPACE)
163
+ token = match(T_ATOM)
164
+ name = token.value.upcase
165
+ match(T_SPACE)
166
+ return TaggedResponse.new(tag, name, resp_text, @str)
167
+ end
168
+
169
+ def response_cond
170
+ token = match(T_ATOM)
171
+ name = token.value.upcase
172
+ match(T_SPACE)
173
+ return UntaggedResponse.new(name, resp_text, @str)
174
+ end
175
+
176
+ def numeric_response
177
+ n = number
178
+ match(T_SPACE)
179
+ token = match(T_ATOM)
180
+ name = token.value.upcase
181
+ case name
182
+ when "EXISTS", "RECENT", "EXPUNGE"
183
+ return UntaggedResponse.new(name, n, @str)
184
+ when "FETCH"
185
+ shift_token
186
+ match(T_SPACE)
187
+ data = FetchData.new(n, msg_att(n))
188
+ return UntaggedResponse.new(name, data, @str)
189
+ end
190
+ end
191
+
192
+ def msg_att(n)
193
+ match(T_LPAR)
194
+ attr = {}
195
+ while true
196
+ token = lookahead
197
+ case token.symbol
198
+ when T_RPAR
199
+ shift_token
200
+ break
201
+ when T_SPACE
202
+ shift_token
203
+ next
204
+ end
205
+ case token.value
206
+ when /\A(?:ENVELOPE)\z/ni
207
+ name, val = envelope_data
208
+ when /\A(?:FLAGS)\z/ni
209
+ name, val = flags_data
210
+ when /\A(?:INTERNALDATE)\z/ni
211
+ name, val = internaldate_data
212
+ when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
213
+ name, val = rfc822_text
214
+ when /\A(?:RFC822\.SIZE)\z/ni
215
+ name, val = rfc822_size
216
+ when /\A(?:BODY(?:STRUCTURE)?)\z/ni
217
+ name, val = body_data
218
+ when /\A(?:UID)\z/ni
219
+ name, val = uid_data
220
+ when /\A(?:MODSEQ)\z/ni
221
+ name, val = modseq_data
222
+ else
223
+ parse_error("unknown attribute `%s' for {%d}", token.value, n)
224
+ end
225
+ attr[name] = val
226
+ end
227
+ return attr
228
+ end
229
+
230
+ def envelope_data
231
+ token = match(T_ATOM)
232
+ name = token.value.upcase
233
+ match(T_SPACE)
234
+ return name, envelope
235
+ end
236
+
237
+ def envelope
238
+ @lex_state = EXPR_DATA
239
+ token = lookahead
240
+ if token.symbol == T_NIL
241
+ shift_token
242
+ result = nil
243
+ else
244
+ match(T_LPAR)
245
+ date = nstring
246
+ match(T_SPACE)
247
+ subject = nstring
248
+ match(T_SPACE)
249
+ from = address_list
250
+ match(T_SPACE)
251
+ sender = address_list
252
+ match(T_SPACE)
253
+ reply_to = address_list
254
+ match(T_SPACE)
255
+ to = address_list
256
+ match(T_SPACE)
257
+ cc = address_list
258
+ match(T_SPACE)
259
+ bcc = address_list
260
+ match(T_SPACE)
261
+ in_reply_to = nstring
262
+ match(T_SPACE)
263
+ message_id = nstring
264
+ match(T_RPAR)
265
+ result = Envelope.new(date, subject, from, sender, reply_to,
266
+ to, cc, bcc, in_reply_to, message_id)
267
+ end
268
+ @lex_state = EXPR_BEG
269
+ return result
270
+ end
271
+
272
+ def flags_data
273
+ token = match(T_ATOM)
274
+ name = token.value.upcase
275
+ match(T_SPACE)
276
+ return name, flag_list
277
+ end
278
+
279
+ def internaldate_data
280
+ token = match(T_ATOM)
281
+ name = token.value.upcase
282
+ match(T_SPACE)
283
+ token = match(T_QUOTED)
284
+ return name, token.value
285
+ end
286
+
287
+ def rfc822_text
288
+ token = match(T_ATOM)
289
+ name = token.value.upcase
290
+ token = lookahead
291
+ if token.symbol == T_LBRA
292
+ shift_token
293
+ match(T_RBRA)
294
+ end
295
+ match(T_SPACE)
296
+ return name, nstring
297
+ end
298
+
299
+ def rfc822_size
300
+ token = match(T_ATOM)
301
+ name = token.value.upcase
302
+ match(T_SPACE)
303
+ return name, number
304
+ end
305
+
306
+ def body_data
307
+ token = match(T_ATOM)
308
+ name = token.value.upcase
309
+ token = lookahead
310
+ if token.symbol == T_SPACE
311
+ shift_token
312
+ return name, body
313
+ end
314
+ name.concat(section)
315
+ token = lookahead
316
+ if token.symbol == T_ATOM
317
+ name.concat(token.value)
318
+ shift_token
319
+ end
320
+ match(T_SPACE)
321
+ data = nstring
322
+ return name, data
323
+ end
324
+
325
+ def body
326
+ @lex_state = EXPR_DATA
327
+ token = lookahead
328
+ if token.symbol == T_NIL
329
+ shift_token
330
+ result = nil
331
+ else
332
+ match(T_LPAR)
333
+ token = lookahead
334
+ if token.symbol == T_LPAR
335
+ result = body_type_mpart
336
+ else
337
+ result = body_type_1part
338
+ end
339
+ match(T_RPAR)
340
+ end
341
+ @lex_state = EXPR_BEG
342
+ return result
343
+ end
344
+
345
+ def body_type_1part
346
+ token = lookahead
347
+ case token.value
348
+ when /\A(?:TEXT)\z/ni
349
+ return body_type_text
350
+ when /\A(?:MESSAGE)\z/ni
351
+ return body_type_msg
352
+ when /\A(?:ATTACHMENT)\z/ni
353
+ return body_type_attachment
354
+ when /\A(?:MIXED)\z/ni
355
+ return body_type_mixed
356
+ else
357
+ return body_type_basic
358
+ end
359
+ end
360
+
361
+ def body_type_basic
362
+ mtype, msubtype = media_type
363
+ token = lookahead
364
+ if token.symbol == T_RPAR
365
+ return BodyTypeBasic.new(mtype, msubtype)
366
+ end
367
+ match(T_SPACE)
368
+ param, content_id, desc, enc, size = body_fields
369
+ md5, disposition, language, extension = body_ext_1part
370
+ return BodyTypeBasic.new(mtype, msubtype,
371
+ param, content_id,
372
+ desc, enc, size,
373
+ md5, disposition, language, extension)
374
+ end
375
+
376
+ def body_type_text
377
+ mtype, msubtype = media_type
378
+ match(T_SPACE)
379
+ param, content_id, desc, enc, size = body_fields
380
+ match(T_SPACE)
381
+ lines = number
382
+ md5, disposition, language, extension = body_ext_1part
383
+ return BodyTypeText.new(mtype, msubtype,
384
+ param, content_id,
385
+ desc, enc, size,
386
+ lines,
387
+ md5, disposition, language, extension)
388
+ end
389
+
390
+ def body_type_msg
391
+ mtype, msubtype = media_type
392
+ match(T_SPACE)
393
+ param, content_id, desc, enc, size = body_fields
394
+
395
+ token = lookahead
396
+ if token.symbol == T_RPAR
397
+ # If this is not message/rfc822, we shouldn't apply the RFC822
398
+ # spec to it. We should handle anything other than
399
+ # message/rfc822 using multipart extension data [rfc3501] (i.e.
400
+ # the data itself won't be returned, we would have to retrieve it
401
+ # with BODYSTRUCTURE instead of with BODY
402
+
403
+ # Also, sometimes a message/rfc822 is included as a large
404
+ # attachment instead of having all of the other details
405
+ # (e.g. attaching a .eml file to an email)
406
+ if msubtype == "RFC822"
407
+ return BodyTypeMessage.new(mtype, msubtype, param, content_id,
408
+ desc, enc, size, nil, nil, nil, nil,
409
+ nil, nil, nil)
410
+ else
411
+ return BodyTypeExtension.new(mtype, msubtype,
412
+ param, content_id,
413
+ desc, enc, size)
414
+ end
415
+ end
416
+
417
+ match(T_SPACE)
418
+ env = envelope
419
+ match(T_SPACE)
420
+ b = body
421
+ match(T_SPACE)
422
+ lines = number
423
+ md5, disposition, language, extension = body_ext_1part
424
+ return BodyTypeMessage.new(mtype, msubtype,
425
+ param, content_id,
426
+ desc, enc, size,
427
+ env, b, lines,
428
+ md5, disposition, language, extension)
429
+ end
430
+
431
+ def body_type_attachment
432
+ mtype = case_insensitive_string
433
+ match(T_SPACE)
434
+ param = body_fld_param
435
+ return BodyTypeAttachment.new(mtype, nil, param)
436
+ end
437
+
438
+ def body_type_mixed
439
+ mtype = "MULTIPART"
440
+ msubtype = case_insensitive_string
441
+ param, disposition, language, extension = body_ext_mpart
442
+ return BodyTypeBasic.new(mtype, msubtype, param, nil, nil, nil, nil, nil, disposition, language, extension)
443
+ end
444
+
445
+ def body_type_mpart
446
+ parts = []
447
+ while true
448
+ token = lookahead
449
+ if token.symbol == T_SPACE
450
+ shift_token
451
+ break
452
+ end
453
+ parts.push(body)
454
+ end
455
+ mtype = "MULTIPART"
456
+ msubtype = case_insensitive_string
457
+ param, disposition, language, extension = body_ext_mpart
458
+ return BodyTypeMultipart.new(mtype, msubtype, parts,
459
+ param, disposition, language,
460
+ extension)
461
+ end
462
+
463
+ def media_type
464
+ mtype = case_insensitive_string
465
+ token = lookahead
466
+ if token.symbol != T_SPACE
467
+ return mtype, nil
468
+ end
469
+ match(T_SPACE)
470
+ msubtype = case_insensitive_string
471
+ return mtype, msubtype
472
+ end
473
+
474
+ def body_fields
475
+ param = body_fld_param
476
+ match(T_SPACE)
477
+ content_id = nstring
478
+ match(T_SPACE)
479
+ desc = nstring
480
+ match(T_SPACE)
481
+ enc = case_insensitive_string
482
+ match(T_SPACE)
483
+ size = number
484
+ return param, content_id, desc, enc, size
485
+ end
486
+
487
+ def body_fld_param
488
+ token = lookahead
489
+ if token.symbol == T_NIL
490
+ shift_token
491
+ return nil
492
+ end
493
+ match(T_LPAR)
494
+ param = {}
495
+ while true
496
+ token = lookahead
497
+ case token.symbol
498
+ when T_RPAR
499
+ shift_token
500
+ break
501
+ when T_SPACE
502
+ shift_token
503
+ end
504
+ name = case_insensitive_string
505
+ match(T_SPACE)
506
+ val = string
507
+ param[name] = val
508
+ end
509
+ return param
510
+ end
511
+
512
+ def body_ext_1part
513
+ token = lookahead
514
+ if token.symbol == T_SPACE
515
+ shift_token
516
+ else
517
+ return nil
518
+ end
519
+ md5 = nstring
520
+
521
+ token = lookahead
522
+ if token.symbol == T_SPACE
523
+ shift_token
524
+ else
525
+ return md5
526
+ end
527
+ disposition = body_fld_dsp
528
+
529
+ token = lookahead
530
+ if token.symbol == T_SPACE
531
+ shift_token
532
+ else
533
+ return md5, disposition
534
+ end
535
+ language = body_fld_lang
536
+
537
+ token = lookahead
538
+ if token.symbol == T_SPACE
539
+ shift_token
540
+ else
541
+ return md5, disposition, language
542
+ end
543
+
544
+ extension = body_extensions
545
+ return md5, disposition, language, extension
546
+ end
547
+
548
+ def body_ext_mpart
549
+ token = lookahead
550
+ if token.symbol == T_SPACE
551
+ shift_token
552
+ else
553
+ return nil
554
+ end
555
+ param = body_fld_param
556
+
557
+ token = lookahead
558
+ if token.symbol == T_SPACE
559
+ shift_token
560
+ else
561
+ return param
562
+ end
563
+ disposition = body_fld_dsp
564
+
565
+ token = lookahead
566
+ if token.symbol == T_SPACE
567
+ shift_token
568
+ else
569
+ return param, disposition
570
+ end
571
+ language = body_fld_lang
572
+
573
+ token = lookahead
574
+ if token.symbol == T_SPACE
575
+ shift_token
576
+ else
577
+ return param, disposition, language
578
+ end
579
+
580
+ extension = body_extensions
581
+ return param, disposition, language, extension
582
+ end
583
+
584
+ def body_fld_dsp
585
+ token = lookahead
586
+ if token.symbol == T_NIL
587
+ shift_token
588
+ return nil
589
+ end
590
+ match(T_LPAR)
591
+ dsp_type = case_insensitive_string
592
+ match(T_SPACE)
593
+ param = body_fld_param
594
+ match(T_RPAR)
595
+ return ContentDisposition.new(dsp_type, param)
596
+ end
597
+
598
+ def body_fld_lang
599
+ token = lookahead
600
+ if token.symbol == T_LPAR
601
+ shift_token
602
+ result = []
603
+ while true
604
+ token = lookahead
605
+ case token.symbol
606
+ when T_RPAR
607
+ shift_token
608
+ return result
609
+ when T_SPACE
610
+ shift_token
611
+ end
612
+ result.push(case_insensitive_string)
613
+ end
614
+ else
615
+ lang = nstring
616
+ if lang
617
+ return lang.upcase
618
+ else
619
+ return lang
620
+ end
621
+ end
622
+ end
623
+
624
+ def body_extensions
625
+ result = []
626
+ while true
627
+ token = lookahead
628
+ case token.symbol
629
+ when T_RPAR
630
+ return result
631
+ when T_SPACE
632
+ shift_token
633
+ end
634
+ result.push(body_extension)
635
+ end
636
+ end
637
+
638
+ def body_extension
639
+ token = lookahead
640
+ case token.symbol
641
+ when T_LPAR
642
+ shift_token
643
+ result = body_extensions
644
+ match(T_RPAR)
645
+ return result
646
+ when T_NUMBER
647
+ return number
648
+ else
649
+ return nstring
650
+ end
651
+ end
652
+
653
+ def section
654
+ str = String.new
655
+ token = match(T_LBRA)
656
+ str.concat(token.value)
657
+ token = match(T_ATOM, T_NUMBER, T_RBRA)
658
+ if token.symbol == T_RBRA
659
+ str.concat(token.value)
660
+ return str
661
+ end
662
+ str.concat(token.value)
663
+ token = lookahead
664
+ if token.symbol == T_SPACE
665
+ shift_token
666
+ str.concat(token.value)
667
+ token = match(T_LPAR)
668
+ str.concat(token.value)
669
+ while true
670
+ token = lookahead
671
+ case token.symbol
672
+ when T_RPAR
673
+ str.concat(token.value)
674
+ shift_token
675
+ break
676
+ when T_SPACE
677
+ shift_token
678
+ str.concat(token.value)
679
+ end
680
+ str.concat(format_string(astring))
681
+ end
682
+ end
683
+ token = match(T_RBRA)
684
+ str.concat(token.value)
685
+ return str
686
+ end
687
+
688
+ def format_string(str)
689
+ case str
690
+ when ""
691
+ return '""'
692
+ when /[\x80-\xff\r\n]/n
693
+ # literal
694
+ return "{" + str.bytesize.to_s + "}" + CRLF + str
695
+ when /[(){ \x00-\x1f\x7f%*"\\]/n
696
+ # quoted string
697
+ return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
698
+ else
699
+ # atom
700
+ return str
701
+ end
702
+ end
703
+
704
+ def uid_data
705
+ token = match(T_ATOM)
706
+ name = token.value.upcase
707
+ match(T_SPACE)
708
+ return name, number
709
+ end
710
+
711
+ def modseq_data
712
+ token = match(T_ATOM)
713
+ name = token.value.upcase
714
+ match(T_SPACE)
715
+ match(T_LPAR)
716
+ modseq = number
717
+ match(T_RPAR)
718
+ return name, modseq
719
+ end
720
+
721
+ def ignored_response
722
+ while lookahead.symbol != T_CRLF
723
+ shift_token
724
+ end
725
+ return IgnoredResponse.new(@str)
726
+ end
727
+
728
+ def text_response
729
+ token = match(T_ATOM)
730
+ name = token.value.upcase
731
+ match(T_SPACE)
732
+ return UntaggedResponse.new(name, text)
733
+ end
734
+
735
+ def flags_response
736
+ token = match(T_ATOM)
737
+ name = token.value.upcase
738
+ match(T_SPACE)
739
+ return UntaggedResponse.new(name, flag_list, @str)
740
+ end
741
+
742
+ def list_response
743
+ token = match(T_ATOM)
744
+ name = token.value.upcase
745
+ match(T_SPACE)
746
+ return UntaggedResponse.new(name, mailbox_list, @str)
747
+ end
748
+
749
+ def mailbox_list
750
+ attr = flag_list
751
+ match(T_SPACE)
752
+ token = match(T_QUOTED, T_NIL)
753
+ if token.symbol == T_NIL
754
+ delim = nil
755
+ else
756
+ delim = token.value
757
+ end
758
+ match(T_SPACE)
759
+ name = astring
760
+ return MailboxList.new(attr, delim, name)
761
+ end
762
+
763
+ def getquota_response
764
+ # If quota never established, get back
765
+ # `NO Quota root does not exist'.
766
+ # If quota removed, get `()' after the
767
+ # folder spec with no mention of `STORAGE'.
768
+ token = match(T_ATOM)
769
+ name = token.value.upcase
770
+ match(T_SPACE)
771
+ mailbox = astring
772
+ match(T_SPACE)
773
+ match(T_LPAR)
774
+ token = lookahead
775
+ case token.symbol
776
+ when T_RPAR
777
+ shift_token
778
+ data = MailboxQuota.new(mailbox, nil, nil)
779
+ return UntaggedResponse.new(name, data, @str)
780
+ when T_ATOM
781
+ shift_token
782
+ match(T_SPACE)
783
+ token = match(T_NUMBER)
784
+ usage = token.value
785
+ match(T_SPACE)
786
+ token = match(T_NUMBER)
787
+ quota = token.value
788
+ match(T_RPAR)
789
+ data = MailboxQuota.new(mailbox, usage, quota)
790
+ return UntaggedResponse.new(name, data, @str)
791
+ else
792
+ parse_error("unexpected token %s", token.symbol)
793
+ end
794
+ end
795
+
796
+ def getquotaroot_response
797
+ # Similar to getquota, but only admin can use getquota.
798
+ token = match(T_ATOM)
799
+ name = token.value.upcase
800
+ match(T_SPACE)
801
+ mailbox = astring
802
+ quotaroots = []
803
+ while true
804
+ token = lookahead
805
+ break unless token.symbol == T_SPACE
806
+ shift_token
807
+ quotaroots.push(astring)
808
+ end
809
+ data = MailboxQuotaRoot.new(mailbox, quotaroots)
810
+ return UntaggedResponse.new(name, data, @str)
811
+ end
812
+
813
+ def getacl_response
814
+ token = match(T_ATOM)
815
+ name = token.value.upcase
816
+ match(T_SPACE)
817
+ mailbox = astring
818
+ data = []
819
+ token = lookahead
820
+ if token.symbol == T_SPACE
821
+ shift_token
822
+ while true
823
+ token = lookahead
824
+ case token.symbol
825
+ when T_CRLF
826
+ break
827
+ when T_SPACE
828
+ shift_token
829
+ end
830
+ user = astring
831
+ match(T_SPACE)
832
+ rights = astring
833
+ data.push(MailboxACLItem.new(user, rights, mailbox))
834
+ end
835
+ end
836
+ return UntaggedResponse.new(name, data, @str)
837
+ end
838
+
839
+ def search_response
840
+ token = match(T_ATOM)
841
+ name = token.value.upcase
842
+ token = lookahead
843
+ if token.symbol == T_SPACE
844
+ shift_token
845
+ data = []
846
+ while true
847
+ token = lookahead
848
+ case token.symbol
849
+ when T_CRLF
850
+ break
851
+ when T_SPACE
852
+ shift_token
853
+ when T_NUMBER
854
+ data.push(number)
855
+ when T_LPAR
856
+ # TODO: include the MODSEQ value in a response
857
+ shift_token
858
+ match(T_ATOM)
859
+ match(T_SPACE)
860
+ match(T_NUMBER)
861
+ match(T_RPAR)
862
+ end
863
+ end
864
+ else
865
+ data = []
866
+ end
867
+ return UntaggedResponse.new(name, data, @str)
868
+ end
869
+
870
+ def thread_response
871
+ token = match(T_ATOM)
872
+ name = token.value.upcase
873
+ token = lookahead
874
+
875
+ if token.symbol == T_SPACE
876
+ threads = []
877
+
878
+ while true
879
+ shift_token
880
+ token = lookahead
881
+
882
+ case token.symbol
883
+ when T_LPAR
884
+ threads << thread_branch(token)
885
+ when T_CRLF
886
+ break
887
+ end
888
+ end
889
+ else
890
+ # no member
891
+ threads = []
892
+ end
893
+
894
+ return UntaggedResponse.new(name, threads, @str)
895
+ end
896
+
897
+ def thread_branch(token)
898
+ rootmember = nil
899
+ lastmember = nil
900
+
901
+ while true
902
+ shift_token # ignore first T_LPAR
903
+ token = lookahead
904
+
905
+ case token.symbol
906
+ when T_NUMBER
907
+ # new member
908
+ newmember = ThreadMember.new(number, [])
909
+ if rootmember.nil?
910
+ rootmember = newmember
911
+ else
912
+ lastmember.children << newmember
913
+ end
914
+ lastmember = newmember
915
+ when T_SPACE
916
+ # do nothing
917
+ when T_LPAR
918
+ if rootmember.nil?
919
+ # dummy member
920
+ lastmember = rootmember = ThreadMember.new(nil, [])
921
+ end
922
+
923
+ lastmember.children << thread_branch(token)
924
+ when T_RPAR
925
+ break
926
+ end
927
+ end
928
+
929
+ return rootmember
930
+ end
931
+
932
+ def status_response
933
+ token = match(T_ATOM)
934
+ name = token.value.upcase
935
+ match(T_SPACE)
936
+ mailbox = astring
937
+ match(T_SPACE)
938
+ match(T_LPAR)
939
+ attr = {}
940
+ while true
941
+ token = lookahead
942
+ case token.symbol
943
+ when T_RPAR
944
+ shift_token
945
+ break
946
+ when T_SPACE
947
+ shift_token
948
+ end
949
+ token = match(T_ATOM)
950
+ key = token.value.upcase
951
+ match(T_SPACE)
952
+ val = number
953
+ attr[key] = val
954
+ end
955
+ data = StatusData.new(mailbox, attr)
956
+ return UntaggedResponse.new(name, data, @str)
957
+ end
958
+
959
+ def capability_response
960
+ token = match(T_ATOM)
961
+ name = token.value.upcase
962
+ match(T_SPACE)
963
+ UntaggedResponse.new(name, capability_data, @str)
964
+ end
965
+
966
+ def capability_data
967
+ data = []
968
+ while true
969
+ token = lookahead
970
+ case token.symbol
971
+ when T_CRLF, T_RBRA
972
+ break
973
+ when T_SPACE
974
+ shift_token
975
+ next
976
+ end
977
+ data.push(atom.upcase)
978
+ end
979
+ data
980
+ end
981
+
982
+ def id_response
983
+ token = match(T_ATOM)
984
+ name = token.value.upcase
985
+ match(T_SPACE)
986
+ token = match(T_LPAR, T_NIL)
987
+ if token.symbol == T_NIL
988
+ return UntaggedResponse.new(name, nil, @str)
989
+ else
990
+ data = {}
991
+ while true
992
+ token = lookahead
993
+ case token.symbol
994
+ when T_RPAR
995
+ shift_token
996
+ break
997
+ when T_SPACE
998
+ shift_token
999
+ next
1000
+ else
1001
+ key = string
1002
+ match(T_SPACE)
1003
+ val = nstring
1004
+ data[key] = val
1005
+ end
1006
+ end
1007
+ return UntaggedResponse.new(name, data, @str)
1008
+ end
1009
+ end
1010
+
1011
+ def namespace_response
1012
+ @lex_state = EXPR_DATA
1013
+ token = lookahead
1014
+ token = match(T_ATOM)
1015
+ name = token.value.upcase
1016
+ match(T_SPACE)
1017
+ personal = namespaces
1018
+ match(T_SPACE)
1019
+ other = namespaces
1020
+ match(T_SPACE)
1021
+ shared = namespaces
1022
+ @lex_state = EXPR_BEG
1023
+ data = Namespaces.new(personal, other, shared)
1024
+ return UntaggedResponse.new(name, data, @str)
1025
+ end
1026
+
1027
+ def namespaces
1028
+ token = lookahead
1029
+ # empty () is not allowed, so nil is functionally identical to empty.
1030
+ data = []
1031
+ if token.symbol == T_NIL
1032
+ shift_token
1033
+ else
1034
+ match(T_LPAR)
1035
+ loop do
1036
+ data << namespace
1037
+ break unless lookahead.symbol == T_SPACE
1038
+ shift_token
1039
+ end
1040
+ match(T_RPAR)
1041
+ end
1042
+ data
1043
+ end
1044
+
1045
+ def namespace
1046
+ match(T_LPAR)
1047
+ prefix = match(T_QUOTED, T_LITERAL).value
1048
+ match(T_SPACE)
1049
+ delimiter = string
1050
+ extensions = namespace_response_extensions
1051
+ match(T_RPAR)
1052
+ Namespace.new(prefix, delimiter, extensions)
1053
+ end
1054
+
1055
+ def namespace_response_extensions
1056
+ data = {}
1057
+ token = lookahead
1058
+ if token.symbol == T_SPACE
1059
+ shift_token
1060
+ name = match(T_QUOTED, T_LITERAL).value
1061
+ data[name] ||= []
1062
+ match(T_SPACE)
1063
+ match(T_LPAR)
1064
+ loop do
1065
+ data[name].push match(T_QUOTED, T_LITERAL).value
1066
+ break unless lookahead.symbol == T_SPACE
1067
+ shift_token
1068
+ end
1069
+ match(T_RPAR)
1070
+ end
1071
+ data
1072
+ end
1073
+
1074
+ # text = 1*TEXT-CHAR
1075
+ # TEXT-CHAR = <any CHAR except CR and LF>
1076
+ def text
1077
+ match(T_TEXT, lex_state: EXPR_TEXT).value
1078
+ end
1079
+
1080
+ # resp-text = ["[" resp-text-code "]" SP] text
1081
+ def resp_text
1082
+ token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT)
1083
+ case token.symbol
1084
+ when T_LBRA
1085
+ code = resp_text_code
1086
+ match(T_RBRA)
1087
+ accept_space # violating RFC
1088
+ ResponseText.new(code, text)
1089
+ when T_TEXT
1090
+ ResponseText.new(nil, token.value)
1091
+ end
1092
+ end
1093
+
1094
+ # See https://www.rfc-editor.org/errata/rfc3501
1095
+ #
1096
+ # resp-text-code = "ALERT" /
1097
+ # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] /
1098
+ # capability-data / "PARSE" /
1099
+ # "PERMANENTFLAGS" SP "("
1100
+ # [flag-perm *(SP flag-perm)] ")" /
1101
+ # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1102
+ # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
1103
+ # "UNSEEN" SP nz-number /
1104
+ # atom [SP 1*<any TEXT-CHAR except "]">]
1105
+ def resp_text_code
1106
+ token = match(T_ATOM)
1107
+ name = token.value.upcase
1108
+ case name
1109
+ when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
1110
+ result = ResponseCode.new(name, nil)
1111
+ when /\A(?:BADCHARSET)\z/n
1112
+ result = ResponseCode.new(name, charset_list)
1113
+ when /\A(?:CAPABILITY)\z/ni
1114
+ result = ResponseCode.new(name, capability_data)
1115
+ when /\A(?:PERMANENTFLAGS)\z/n
1116
+ match(T_SPACE)
1117
+ result = ResponseCode.new(name, flag_list)
1118
+ when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
1119
+ match(T_SPACE)
1120
+ result = ResponseCode.new(name, number)
1121
+ else
1122
+ token = lookahead
1123
+ if token.symbol == T_SPACE
1124
+ shift_token
1125
+ token = match(T_TEXT, lex_state: EXPR_CTEXT)
1126
+ result = ResponseCode.new(name, token.value)
1127
+ else
1128
+ result = ResponseCode.new(name, nil)
1129
+ end
1130
+ end
1131
+ return result
1132
+ end
1133
+
1134
+ def charset_list
1135
+ result = []
1136
+ if accept(T_SPACE)
1137
+ match(T_LPAR)
1138
+ result << charset
1139
+ while accept(T_SPACE)
1140
+ result << charset
1141
+ end
1142
+ match(T_RPAR)
1143
+ end
1144
+ result
1145
+ end
1146
+
1147
+ def address_list
1148
+ token = lookahead
1149
+ if token.symbol == T_NIL
1150
+ shift_token
1151
+ return nil
1152
+ else
1153
+ result = []
1154
+ match(T_LPAR)
1155
+ while true
1156
+ token = lookahead
1157
+ case token.symbol
1158
+ when T_RPAR
1159
+ shift_token
1160
+ break
1161
+ when T_SPACE
1162
+ shift_token
1163
+ end
1164
+ result.push(address)
1165
+ end
1166
+ return result
1167
+ end
1168
+ end
1169
+
1170
+ ADDRESS_REGEXP = /\G\
1171
+ (?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
1172
+ (?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
1173
+ (?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
1174
+ (?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
1175
+ \)/ni
1176
+
1177
+ def address
1178
+ match(T_LPAR)
1179
+ if @str.index(ADDRESS_REGEXP, @pos)
1180
+ # address does not include literal.
1181
+ @pos = $~.end(0)
1182
+ name = $1
1183
+ route = $2
1184
+ mailbox = $3
1185
+ host = $4
1186
+ for s in [name, route, mailbox, host]
1187
+ if s
1188
+ s.gsub!(/\\(["\\])/n, "\\1")
1189
+ end
1190
+ end
1191
+ else
1192
+ name = nstring
1193
+ match(T_SPACE)
1194
+ route = nstring
1195
+ match(T_SPACE)
1196
+ mailbox = nstring
1197
+ match(T_SPACE)
1198
+ host = nstring
1199
+ match(T_RPAR)
1200
+ end
1201
+ return Address.new(name, route, mailbox, host)
1202
+ end
1203
+
1204
+ FLAG_REGEXP = /\
1205
+ (?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
1206
+ (?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
1207
+
1208
+ def flag_list
1209
+ if @str.index(/\(([^)]*)\)/ni, @pos)
1210
+ @pos = $~.end(0)
1211
+ return $1.scan(FLAG_REGEXP).collect { |flag, atom|
1212
+ if atom
1213
+ atom
1214
+ else
1215
+ symbol = flag.capitalize.intern
1216
+ @flag_symbols[symbol] = true
1217
+ if @flag_symbols.length > IMAP.max_flag_count
1218
+ raise FlagCountError, "number of flag symbols exceeded"
1219
+ end
1220
+ symbol
1221
+ end
1222
+ }
1223
+ else
1224
+ parse_error("invalid flag list")
1225
+ end
1226
+ end
1227
+
1228
+ def nstring
1229
+ token = lookahead
1230
+ if token.symbol == T_NIL
1231
+ shift_token
1232
+ return nil
1233
+ else
1234
+ return string
1235
+ end
1236
+ end
1237
+
1238
+ def astring
1239
+ token = lookahead
1240
+ if string_token?(token)
1241
+ return string
1242
+ else
1243
+ return astring_chars
1244
+ end
1245
+ end
1246
+
1247
+ def string
1248
+ token = lookahead
1249
+ if token.symbol == T_NIL
1250
+ shift_token
1251
+ return nil
1252
+ end
1253
+ token = match(T_QUOTED, T_LITERAL)
1254
+ return token.value
1255
+ end
1256
+
1257
+ STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
1258
+
1259
+ def string_token?(token)
1260
+ return STRING_TOKENS.include?(token.symbol)
1261
+ end
1262
+
1263
+ def case_insensitive_string
1264
+ token = lookahead
1265
+ if token.symbol == T_NIL
1266
+ shift_token
1267
+ return nil
1268
+ end
1269
+ token = match(T_QUOTED, T_LITERAL)
1270
+ return token.value.upcase
1271
+ end
1272
+
1273
+ # atom = 1*ATOM-CHAR
1274
+ # ATOM-CHAR = <any CHAR except atom-specials>
1275
+ ATOM_TOKENS = [
1276
+ T_ATOM,
1277
+ T_NUMBER,
1278
+ T_NIL,
1279
+ T_LBRA,
1280
+ T_PLUS
1281
+ ]
1282
+
1283
+ def atom
1284
+ -combine_adjacent(*ATOM_TOKENS)
1285
+ end
1286
+
1287
+ # ASTRING-CHAR = ATOM-CHAR / resp-specials
1288
+ # resp-specials = "]"
1289
+ ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA]
1290
+
1291
+ def astring_chars
1292
+ combine_adjacent(*ASTRING_CHARS_TOKENS)
1293
+ end
1294
+
1295
+ def combine_adjacent(*tokens)
1296
+ result = "".b
1297
+ while token = accept(*tokens)
1298
+ result << token.value
1299
+ end
1300
+ if result.empty?
1301
+ parse_error('unexpected token %s (expected %s)',
1302
+ lookahead.symbol, args.join(" or "))
1303
+ end
1304
+ result
1305
+ end
1306
+
1307
+ # See https://www.rfc-editor.org/errata/rfc3501
1308
+ #
1309
+ # charset = atom / quoted
1310
+ def charset
1311
+ if token = accept(T_QUOTED)
1312
+ token.value
1313
+ else
1314
+ atom
1315
+ end
1316
+ end
1317
+
1318
+ def number
1319
+ token = lookahead
1320
+ if token.symbol == T_NIL
1321
+ shift_token
1322
+ return nil
1323
+ end
1324
+ token = match(T_NUMBER)
1325
+ return token.value.to_i
1326
+ end
1327
+
1328
+ def nil_atom
1329
+ match(T_NIL)
1330
+ return nil
1331
+ end
1332
+
1333
+ SPACES_REGEXP = /\G */n
1334
+
1335
+ # This advances @pos directly so it's safe before changing @lex_state.
1336
+ def accept_space
1337
+ if @token
1338
+ shift_token if @token.symbol == T_SPACE
1339
+ elsif @str[@pos] == " "
1340
+ @pos += 1
1341
+ end
1342
+ end
1343
+
1344
+ # The RFC is very strict about this and usually we should be too.
1345
+ # But skipping spaces is usually a safe workaround for buggy servers.
1346
+ #
1347
+ # This advances @pos directly so it's safe before changing @lex_state.
1348
+ def accept_spaces
1349
+ shift_token if @token&.symbol == T_SPACE
1350
+ if @str.index(SPACES_REGEXP, @pos)
1351
+ @pos = $~.end(0)
1352
+ end
1353
+ end
1354
+
1355
+ def match(*args, lex_state: @lex_state)
1356
+ if @token && lex_state != @lex_state
1357
+ parse_error("invalid lex_state change to %s with unconsumed token",
1358
+ lex_state)
1359
+ end
1360
+ begin
1361
+ @lex_state, original_lex_state = lex_state, @lex_state
1362
+ token = lookahead
1363
+ unless args.include?(token.symbol)
1364
+ parse_error('unexpected token %s (expected %s)',
1365
+ token.symbol.id2name,
1366
+ args.collect {|i| i.id2name}.join(" or "))
1367
+ end
1368
+ shift_token
1369
+ return token
1370
+ ensure
1371
+ @lex_state = original_lex_state
1372
+ end
1373
+ end
1374
+
1375
+ # like match, but does not raise error on failure.
1376
+ #
1377
+ # returns and shifts token on successful match
1378
+ # returns nil and leaves @token unshifted on no match
1379
+ def accept(*args)
1380
+ token = lookahead
1381
+ if args.include?(token.symbol)
1382
+ shift_token
1383
+ token
1384
+ end
1385
+ end
1386
+
1387
+ def lookahead
1388
+ @token ||= next_token
1389
+ end
1390
+
1391
+ def shift_token
1392
+ @token = nil
1393
+ end
1394
+
1395
+ def next_token
1396
+ case @lex_state
1397
+ when EXPR_BEG
1398
+ if @str.index(BEG_REGEXP, @pos)
1399
+ @pos = $~.end(0)
1400
+ if $1
1401
+ return Token.new(T_SPACE, $+)
1402
+ elsif $2
1403
+ return Token.new(T_NIL, $+)
1404
+ elsif $3
1405
+ return Token.new(T_NUMBER, $+)
1406
+ elsif $4
1407
+ return Token.new(T_ATOM, $+)
1408
+ elsif $5
1409
+ return Token.new(T_QUOTED,
1410
+ $+.gsub(/\\(["\\])/n, "\\1"))
1411
+ elsif $6
1412
+ return Token.new(T_LPAR, $+)
1413
+ elsif $7
1414
+ return Token.new(T_RPAR, $+)
1415
+ elsif $8
1416
+ return Token.new(T_BSLASH, $+)
1417
+ elsif $9
1418
+ return Token.new(T_STAR, $+)
1419
+ elsif $10
1420
+ return Token.new(T_LBRA, $+)
1421
+ elsif $11
1422
+ return Token.new(T_RBRA, $+)
1423
+ elsif $12
1424
+ len = $+.to_i
1425
+ val = @str[@pos, len]
1426
+ @pos += len
1427
+ return Token.new(T_LITERAL, val)
1428
+ elsif $13
1429
+ return Token.new(T_PLUS, $+)
1430
+ elsif $14
1431
+ return Token.new(T_PERCENT, $+)
1432
+ elsif $15
1433
+ return Token.new(T_CRLF, $+)
1434
+ elsif $16
1435
+ return Token.new(T_EOF, $+)
1436
+ else
1437
+ parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
1438
+ end
1439
+ else
1440
+ @str.index(/\S*/n, @pos)
1441
+ parse_error("unknown token - %s", $&.dump)
1442
+ end
1443
+ when EXPR_DATA
1444
+ if @str.index(DATA_REGEXP, @pos)
1445
+ @pos = $~.end(0)
1446
+ if $1
1447
+ return Token.new(T_SPACE, $+)
1448
+ elsif $2
1449
+ return Token.new(T_NIL, $+)
1450
+ elsif $3
1451
+ return Token.new(T_NUMBER, $+)
1452
+ elsif $4
1453
+ return Token.new(T_QUOTED,
1454
+ $+.gsub(/\\(["\\])/n, "\\1"))
1455
+ elsif $5
1456
+ len = $+.to_i
1457
+ val = @str[@pos, len]
1458
+ @pos += len
1459
+ return Token.new(T_LITERAL, val)
1460
+ elsif $6
1461
+ return Token.new(T_LPAR, $+)
1462
+ elsif $7
1463
+ return Token.new(T_RPAR, $+)
1464
+ else
1465
+ parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
1466
+ end
1467
+ else
1468
+ @str.index(/\S*/n, @pos)
1469
+ parse_error("unknown token - %s", $&.dump)
1470
+ end
1471
+ when EXPR_TEXT
1472
+ if @str.index(TEXT_REGEXP, @pos)
1473
+ @pos = $~.end(0)
1474
+ if $1
1475
+ return Token.new(T_TEXT, $+)
1476
+ else
1477
+ parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
1478
+ end
1479
+ else
1480
+ @str.index(/\S*/n, @pos)
1481
+ parse_error("unknown token - %s", $&.dump)
1482
+ end
1483
+ when EXPR_RTEXT
1484
+ if @str.index(RTEXT_REGEXP, @pos)
1485
+ @pos = $~.end(0)
1486
+ if $1
1487
+ return Token.new(T_LBRA, $+)
1488
+ elsif $2
1489
+ return Token.new(T_TEXT, $+)
1490
+ else
1491
+ parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
1492
+ end
1493
+ else
1494
+ @str.index(/\S*/n, @pos)
1495
+ parse_error("unknown token - %s", $&.dump)
1496
+ end
1497
+ when EXPR_CTEXT
1498
+ if @str.index(CTEXT_REGEXP, @pos)
1499
+ @pos = $~.end(0)
1500
+ if $1
1501
+ return Token.new(T_TEXT, $+)
1502
+ else
1503
+ parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
1504
+ end
1505
+ else
1506
+ @str.index(/\S*/n, @pos) #/
1507
+ parse_error("unknown token - %s", $&.dump)
1508
+ end
1509
+ else
1510
+ parse_error("invalid @lex_state - %s", @lex_state.inspect)
1511
+ end
1512
+ end
1513
+
1514
+ def parse_error(fmt, *args)
1515
+ if IMAP.debug
1516
+ $stderr.printf("@str: %s\n", @str.dump)
1517
+ $stderr.printf("@pos: %d\n", @pos)
1518
+ $stderr.printf("@lex_state: %s\n", @lex_state)
1519
+ if @token
1520
+ $stderr.printf("@token.symbol: %s\n", @token.symbol)
1521
+ $stderr.printf("@token.value: %s\n", @token.value.inspect)
1522
+ end
1523
+ end
1524
+ raise ResponseParseError, format(fmt, *args)
1525
+ end
1526
+ end
1527
+
1528
+ end
1529
+
1530
+ end