001 /*
002 * Copyright 2010 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package com.hs.mail.imap.parser;
017
018 import java.io.IOException;
019 import java.io.StringReader;
020 import java.util.LinkedList;
021
022 /**
023 *
024 * @author Won Chul Doh
025 * @since Jan 20, 2010
026 *
027 */
028 public class AbstractImapCommandParser {
029
030 protected StringReader reader;
031 protected char pushback[];
032 protected StringBuffer buffer;
033 protected int pos;
034 protected LinkedList<Token> tokens;
035
036 public AbstractImapCommandParser(StringReader in) {
037 reader = in;
038 pushback = new char[32];
039 buffer = new StringBuffer();
040 pos = -1;
041 tokens = new LinkedList<Token>();
042 }
043
044 /******************************************
045 * THE COMMAND GRAMMAR STARTS HERE *
046 ******************************************/
047
048 protected boolean astring() {
049 if (astring_char(read())) {
050 while (astring_char(read()))
051 ;
052 unread();
053 newToken(Token.Type.ASTRING);
054 return true;
055 } else {
056 unread();
057 return string();
058 }
059 }
060
061 protected boolean astring_char(char c) {
062 return atom_char(c) || resp_special(c);
063 }
064
065 protected boolean atom() {
066 if (atom_char(read())) {
067 while (atom_char(read()))
068 ;
069 unread();
070 newToken(Token.Type.ATOM);
071 return true;
072 } else {
073 unread();
074 return false;
075 }
076 }
077
078 protected boolean atom_char(char c) {
079 return !atom_specials(c);
080 }
081
082 protected boolean atom_specials(char c) {
083 /* return c == '(' || c == ')' || c == '{' || c == ' ' || c <= '\037'
084 || c >= '\177' || list_wildcard(c) || quoted_special(c)
085 || resp_special(c);
086 */ return c == '(' || c == ')' || c == '{' || c == ' ' || c < 32
087 || c == 127 || list_wildcard(c) || quoted_special(c)
088 || resp_special(c);
089 }
090
091 protected boolean date() {
092 boolean dquote = false;
093 if (read() == '"')
094 dquote = true;
095 else
096 unread();
097 if (!date_text()) {
098 unreadAll();
099 return false;
100 }
101 if (dquote && read() != '"') {
102 unreadAll();
103 return false;
104 }
105 if (dquote)
106 undquote();
107 newToken(Token.Type.DATE);
108 return true;
109 }
110
111 private boolean date_day() {
112 return _number(2) || _number(1);
113 }
114
115 private boolean date_day_fixed() {
116 return (_sp() && _number(1)) || _number(2);
117 }
118
119 private boolean date_month() {
120 return _kw("Jan") || _kw("Feb") || _kw("Mar") || _kw("Apr")
121 || _kw("May") || _kw("Jun") || _kw("Jul") || _kw("Aug")
122 || _kw("Sep") || _kw("Oct") || _kw("Nov") || _kw("Dec");
123 }
124
125 private boolean date_text() {
126 return date_day() && _kw("-") && date_month() && _kw("-")
127 && date_year();
128 }
129
130 private boolean date_year() {
131 return _number(4);
132 }
133
134 protected boolean date_time() {
135 if (read() != '"') {
136 unread();
137 return false;
138 }
139 if (!date_day_fixed() || !_kw("-") || !date_month() || !_kw("-")
140 || !date_year() || !_sp() || !time() || !_sp() || !zone()) {
141 unreadAll();
142 return false;
143 }
144 if (read() != '"') {
145 unreadAll();
146 return false;
147 } else {
148 undquote();
149 newToken(Token.Type.DATE_TIME);
150 return true;
151 }
152 }
153
154 protected boolean list_char(char c) {
155 return atom_char(c) || list_wildcard(c) || resp_special(c);
156 }
157
158 protected boolean list_wildcard(char c) {
159 return c == '*' || c == '%';
160 }
161
162 protected boolean literal() {
163 if (read() != '{') {
164 unread();
165 return false;
166 }
167 if (!_number()) {
168 unreadAll();
169 return false;
170 }
171 boolean sync = false;
172 if (read() != '+') {
173 unread();
174 } else {
175 buffer.setLength(buffer.length() - 1);
176 sync = true;
177 }
178 if (read() != '}') {
179 unreadAll();
180 return false;
181 }
182 int length = Integer.parseInt(buffer.substring(1, buffer.length() - 1));
183 buffer.setLength(0);
184 buffer.append(length);
185 newToken((sync) ? Token.Type.LITERAL_SYNC : Token.Type.LITERAL);
186 return true;
187 }
188
189 protected boolean number() {
190 return number(0, true);
191 }
192
193 protected boolean _number() {
194 return number(0, false);
195 }
196
197 protected boolean number(int digits) {
198 return number(digits, true);
199 }
200
201 protected boolean _number(int digits) {
202 return number(digits, false);
203 }
204
205 private boolean number(int digits, boolean tokenize) {
206 if (digit(read())) {
207 int count;
208 for (count = 1; digit(read()); count++)
209 ;
210 unread();
211 if (digits > 0 && count != digits) {
212 for (int j = count; j > 0; j--)
213 unread();
214
215 return false;
216 }
217 if (tokenize)
218 newToken(Token.Type.NUMBER);
219 return true;
220 } else {
221 unread();
222 return false;
223 }
224 }
225
226 protected boolean nz_number() {
227 if (nz_digit(read())) {
228 while (digit(read()))
229 ;
230 unread();
231 newToken(Token.Type.NZ_NUMBER);
232 return true;
233 } else {
234 unread();
235 return false;
236 }
237 }
238
239 protected boolean quoted() {
240 if (read() != '"') {
241 unread();
242 return false;
243 }
244 while (quoted_char(read()))
245 ;
246 unread();
247 if (read() != '"') {
248 unreadAll();
249 return false;
250 } else {
251 unEscape();
252 undquote();
253 newToken(Token.Type.QUOTED);
254 return true;
255 }
256 }
257
258 protected boolean quoted_char(char c) {
259 if (c == '\\') {
260 char _c = read();
261 if (_c != '"' && _c != '\\') {
262 unread();
263 return false;
264 } else {
265 return true;
266 }
267 } else {
268 return text_char(c) && !quoted_special(c);
269 }
270 }
271
272 protected boolean quoted_special(char c) {
273 return c == '"' || c == '\\';
274 }
275
276 protected boolean resp_special(char c) {
277 return c == ']';
278 }
279
280 private boolean seq_number() {
281 return seq_number(true);
282 }
283
284 private boolean _seq_number() {
285 return seq_number(false);
286 }
287
288 private boolean seq_number(boolean tokenize) {
289 if (!_number() && !_kw("*"))
290 return false;
291 if (tokenize)
292 newToken(Token.Type.SEQ_NUMBER);
293 return true;
294 }
295
296 private boolean seq_range() {
297 if (!_seq_number() || !_kw(":") || !_seq_number()) {
298 unreadAll();
299 return false;
300 } else {
301 newToken(Token.Type.SEQ_RANGE);
302 return true;
303 }
304 }
305
306 protected boolean sequence_set() {
307 if (!seq_range() && !seq_number())
308 return false;
309 if (kw(","))
310 do
311 if (!sequence_set())
312 return false;
313 while (kw(","));
314 return true;
315 }
316
317 protected boolean string() {
318 return quoted() || literal();
319 }
320
321 private boolean time() {
322 return _number(2) && _kw(":") && _number(2) && _kw(":") && _number(2);
323 }
324
325 private boolean zone() {
326 return (_kw("+") || !_kw("-")) && _number(4);
327 }
328
329 /******************************************
330 * THE COMMAND GRAMMAR ENDS HERE *
331 ******************************************/
332
333 protected boolean crlf() {
334 if (read() != '\r') {
335 unread();
336 return false;
337 }
338 if (read() != '\n') {
339 unread();
340 return false;
341 } else {
342 buffer.setLength(0);
343 return true;
344 }
345 }
346
347 private boolean digit(char c) {
348 return c >= '0' && c <= '9';
349 }
350
351 protected boolean kw(String pattern) {
352 return kw(pattern, true);
353 }
354
355 protected boolean _kw(String pattern) {
356 return kw(pattern, false);
357 }
358
359 private boolean kw(String pattern, boolean tokenize) {
360 pattern = pattern.toUpperCase();
361 int i = 0;
362 for (int n = pattern.length(); i < n; i++) {
363 char c = read();
364 if (Character.toUpperCase(c) != pattern.charAt(i)) {
365 for (int j = i; j >= 0; j--)
366 unread();
367
368 return false;
369 }
370 }
371
372 if (tokenize)
373 newToken(Token.Type.KEYWORD);
374 return true;
375 }
376
377 protected boolean lparen() {
378 if (read() == '(') {
379 newToken(Token.Type.LPAREN);
380 return true;
381 } else {
382 unread();
383 return false;
384 }
385 }
386
387 private boolean nz_digit(char c) {
388 return c >= '1' && c <= '9';
389 }
390
391 protected boolean rparen() {
392 if (read() == ')') {
393 newToken(Token.Type.RPAREN);
394 return true;
395 } else {
396 unread();
397 return false;
398 }
399 }
400
401 protected boolean sp() {
402 return sp(true);
403 }
404
405 protected boolean _sp() {
406 return sp(false);
407 }
408
409 private boolean sp(boolean tokenize) {
410 if (read() == ' ') {
411 if (tokenize)
412 buffer.setLength(0);
413 return true;
414 } else {
415 unread();
416 return false;
417 }
418 }
419
420 protected boolean tag_char(char c) {
421 return astring_char(c) && c != '+';
422 }
423
424 private boolean text_char(char c) {
425 return c >= '\001' && c <= '\377' && c != '\r' && c != '\n';
426 }
427
428 protected char read() {
429 char c;
430 if (pos >= 0) {
431 c = pushback[pos--];
432 } else {
433 try {
434 int i = reader.read();
435 if (i == -1)
436 throw new ParseException(tokens,
437 "Unexpected end of stream");
438 c = (char) i;
439 } catch (IOException e) {
440 throw new ParseException(tokens,
441 "Error while reading character", e);
442 }
443 }
444 buffer.append(c);
445 return c;
446 }
447
448 protected void unread() {
449 int len = buffer.length();
450 if (pos == pushback.length) {
451 throw new ParseException(tokens, "Pushback buffer too short");
452 } else {
453 pushback[++pos] = buffer.charAt(len - 1);
454 buffer.setLength(len - 1);
455 return;
456 }
457 }
458
459 protected void unreadAll() {
460 for (; buffer.length() > 0; unread())
461 ;
462 }
463
464 private void unEscape() {
465 for (int i = 1; i < buffer.length() - 1; i++) {
466 char c = buffer.charAt(i);
467 if (c == '\\')
468 buffer.deleteCharAt(i);
469 }
470 }
471
472 private void undquote() {
473 buffer.deleteCharAt(0);
474 buffer.deleteCharAt(buffer.length() - 1);
475 }
476
477 protected void newToken(Token.Type type) {
478 if (buffer.length() > 1024)
479 throw new ParseException(tokens,
480 "Command line length exceeds fixed limit");
481 String value = buffer.toString();
482 Token token = new Token(type, value);
483 tokens.add(token);
484 buffer.setLength(0);
485 }
486
487 }