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 }