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    }