1 17 18 package org.apache.james.imapserver.commands; 19 20 import org.apache.james.imapserver.ProtocolException; 21 import org.apache.james.imapserver.ImapRequestLineReader; 22 import org.apache.james.imapserver.ImapConstants; 23 import org.apache.james.imapserver.store.MessageFlags; 24 import org.apache.james.util.Assert; 25 26 import java.util.Date ; 27 import java.util.TimeZone ; 28 import java.util.List ; 29 import java.util.ArrayList ; 30 import java.text.DateFormat ; 31 import java.text.SimpleDateFormat ; 32 import java.text.ParseException ; 33 34 39 public class CommandParser 40 { 41 private static final char[] EMPTY_CHAR_ARRAY = new char[0]; 42 43 46 public String atom( ImapRequestLineReader request ) throws ProtocolException 47 { 48 return consumeWord( request, new ATOM_CHARValidator() ); 49 } 50 51 54 public String tag(ImapRequestLineReader request) throws ProtocolException 55 { 56 CharacterValidator validator = new TagCharValidator(); 57 return consumeWord( request, validator ); 58 } 59 60 63 public String astring(ImapRequestLineReader request) throws ProtocolException 64 { 65 char next = request.nextWordChar(); 66 switch ( next ) { 67 case '"': 68 return consumeQuoted( request ); 69 case '{': 70 return consumeLiteral( request ); 71 default: 72 return atom( request ); 73 } 74 } 75 76 79 public String nstring( ImapRequestLineReader request ) throws ProtocolException 80 { 81 char next = request.nextWordChar(); 82 switch ( next ) { 83 case '"': 84 return consumeQuoted( request ); 85 case '{': 86 return consumeLiteral( request ); 87 default: 88 String value = atom( request ); 89 if ( "NIL".equals( value ) ) { 90 return null; 91 } 92 else { 93 throw new ProtocolException( "Invalid nstring value: valid values are '\"...\"', '{12} CRLF *CHAR8', and 'NIL'." ); 94 } 95 } 96 } 97 98 108 public String mailbox( ImapRequestLineReader request ) throws ProtocolException 109 { 110 String mailbox = astring( request ); 111 if ( mailbox.equalsIgnoreCase( ImapConstants.INBOX_NAME ) ) { 112 return ImapConstants.INBOX_NAME; 113 } 114 else { 115 return mailbox; 116 } 117 } 118 119 123 public Date dateTime( ImapRequestLineReader request ) throws ProtocolException 124 { 125 char next = request.nextWordChar(); 126 String dateString; 127 if ( next == '"' ) { 128 dateString = consumeQuoted( request ); 129 } 130 else { 131 throw new ProtocolException( "DateTime values must be quoted." ); 132 } 133 134 DateFormat dateFormat = new SimpleDateFormat ( "dd-MMM-yyyy hh:mm:ss zzzz" ); 135 try { 136 return dateFormat.parse( dateString ); 137 } 138 catch ( ParseException e ) { 139 throw new ProtocolException( "Invalid date format." ); 140 } 141 } 142 143 147 public Date date( ImapRequestLineReader request ) throws ProtocolException 148 { 149 char next = request.nextWordChar(); 150 String dateString; 151 if ( next == '"' ) { 152 dateString = consumeQuoted( request ); 153 } 154 else { 155 dateString = atom( request ); 156 } 157 158 DateFormat dateFormat = new SimpleDateFormat ( "dd-MMM-yyyy" ); 159 try { 160 return dateFormat.parse( dateString ); 161 } 162 catch ( ParseException e ) { 163 throw new ProtocolException( "Invalid date format." ); 164 } 165 } 166 167 172 protected String consumeWord( ImapRequestLineReader request, 173 CharacterValidator validator ) 174 throws ProtocolException 175 { 176 StringBuffer atom = new StringBuffer (); 177 178 char next = request.nextWordChar(); 179 while( ! isWhitespace( next ) ) { 180 if ( validator.isValid( next ) ) 181 { 182 atom.append( next ); 183 request.consume(); 184 } 185 else { 186 throw new ProtocolException( "Invalid character: '" + next + "'" ); 187 } 188 next = request.nextChar(); 189 } 190 return atom.toString(); 191 } 192 193 private boolean isWhitespace( char next ) 194 { 195 return ( next == ' ' || next == '\n' || next == '\r' || next == '\t' ); 196 } 197 198 204 protected String consumeLiteral( ImapRequestLineReader request ) 205 throws ProtocolException 206 { 207 consumeChar( request, '{' ); 209 210 StringBuffer digits = new StringBuffer (); 211 char next = request.nextChar(); 212 while ( next != '}' && next != '+' ) 213 { 214 digits.append( next ); 215 request.consume(); 216 next = request.nextChar(); 217 } 218 219 boolean synchronizedLiteral = true; 222 if ( next == '+' ) { 224 synchronizedLiteral = false; 225 consumeChar(request, '+' ); 226 } 227 228 consumeChar( request, '}' ); 230 consumeCRLF( request ); 231 232 if ( synchronizedLiteral ) { 233 request.commandContinuationRequest(); 234 } 235 236 int size = Integer.parseInt( digits.toString() ); 237 byte[] buffer = new byte[size]; 238 request.read( buffer ); 239 240 return new String ( buffer ); 241 } 242 243 249 private void consumeCRLF( ImapRequestLineReader request ) 250 throws ProtocolException 251 { 252 char next = request.nextChar(); 253 if ( next != '\n' ) { 254 consumeChar( request, '\r' ); 255 } 256 consumeChar( request, '\n' ); 257 } 258 259 263 protected void consumeChar( ImapRequestLineReader request, char expected ) 264 throws ProtocolException 265 { 266 char consumed = request.consume(); 267 if ( consumed != expected ) { 268 throw new ProtocolException( "Expected:'" + expected + "' found:'" + consumed + "'" ); 269 } 270 } 271 272 275 protected String consumeQuoted( ImapRequestLineReader request ) 276 throws ProtocolException 277 { 278 consumeChar(request, '"' ); 280 281 StringBuffer quoted = new StringBuffer (); 282 char next = request.nextChar(); 283 while( next != '"' ) { 284 if ( next == '\\' ) { 285 request.consume(); 286 next = request.nextChar(); 287 if ( ! isQuotedSpecial( next ) ) { 288 throw new ProtocolException( "Invalid escaped character in quote: '" + 289 next + "'" ); 290 } 291 } 292 quoted.append( next ); 293 request.consume(); 294 next = request.nextChar(); 295 } 296 297 consumeChar( request, '"' ); 298 299 return quoted.toString(); 300 } 301 302 305 public byte[] base64( ImapRequestLineReader request ) throws ProtocolException 306 { 307 return null; 308 } 309 310 313 public MessageFlags flagList( ImapRequestLineReader request ) throws ProtocolException 314 { 315 MessageFlags flags = new MessageFlags(); 316 request.nextWordChar(); 317 consumeChar( request, '(' ); 318 CharacterValidator validator = new NoopCharValidator(); 319 String nextWord = consumeWord( request, validator ); 320 while ( ! nextWord.endsWith(")" ) ) { 321 setFlag( nextWord, flags ); 322 nextWord = consumeWord( request, validator ); 323 } 324 if ( nextWord.length() > 1 ) { 326 setFlag( nextWord.substring(0, nextWord.length() - 1 ), flags ); 327 } 328 329 return flags; 330 } 331 332 public void setFlag( String flagString, MessageFlags flags ) throws ProtocolException 333 { 334 if ( flagString.equalsIgnoreCase( MessageFlags.ANSWERED ) ) { 335 flags.setAnswered( true ); 336 } 337 else if ( flagString.equalsIgnoreCase( MessageFlags.DELETED ) ) { 338 flags.setDeleted( true ); 339 } 340 else if ( flagString.equalsIgnoreCase( MessageFlags.DRAFT ) ) { 341 flags.setDraft( true ); 342 } 343 else if ( flagString.equalsIgnoreCase( MessageFlags.FLAGGED ) ) { 344 flags.setFlagged( true ); 345 } 346 else if ( flagString.equalsIgnoreCase( MessageFlags.SEEN ) ) { 347 flags.setSeen( true ); 348 } 349 else { 350 throw new ProtocolException( "Invalid flag string." ); 351 } 352 } 353 354 357 public long number( ImapRequestLineReader request ) throws ProtocolException 358 { 359 String digits = consumeWord( request, new DigitCharValidator() ); 360 return Long.parseLong( digits ); 361 } 362 363 369 public long nzNumber( ImapRequestLineReader request ) throws ProtocolException 370 { 371 long number = number( request ); 372 if ( number == 0 ) { 373 throw new ProtocolException( "Zero value not permitted." ); 374 } 375 return number; 376 } 377 378 private boolean isCHAR( char chr ) 379 { 380 return ( chr >= 0x01 && chr <= 0x7f ); 381 } 382 383 private boolean isCHAR8( char chr ) 384 { 385 return ( chr >= 0x01 && chr <= 0xff ); 386 } 387 388 protected boolean isListWildcard( char chr ) 389 { 390 return ( chr == '*' || chr == '%' ); 391 } 392 393 private boolean isQuotedSpecial( char chr ) 394 { 395 return ( chr == '"' || chr == '\\' ); 396 } 397 398 403 public void endLine( ImapRequestLineReader request ) throws ProtocolException 404 { 405 request.eol(); 406 } 407 408 412 public IdSet set( ImapRequestLineReader request ) 413 throws ProtocolException 414 { 415 CharacterValidator validator = new MessageSetCharValidator(); 416 String nextWord = consumeWord( request, validator ); 417 418 int commaPos = nextWord.indexOf( ',' ); 419 if ( commaPos == -1 ) { 420 return singleRangeSet( nextWord ); 421 } 422 423 CompoundIdSet compoundSet = new CompoundIdSet(); 424 int pos = 0; 425 while ( commaPos != -1 ) { 426 String range = nextWord.substring( pos, commaPos ); 427 IdSet set = singleRangeSet( range ); 428 compoundSet.addIdSet( set ); 429 430 pos = commaPos + 1; 431 commaPos = nextWord.indexOf( ',', pos ); 432 } 433 String range = nextWord.substring( pos ); 434 compoundSet.addIdSet( singleRangeSet( range ) ); 435 return compoundSet; 436 } 437 438 private IdSet singleRangeSet( String range ) throws ProtocolException 439 { 440 int pos = range.indexOf( ':' ); 441 try { 442 if ( pos == -1 ) { 443 long value = parseLong( range ); 444 return new HighLowIdSet( value, value ); 445 } 446 else { 447 long lowVal = parseLong( range.substring(0, pos ) ); 448 long highVal = parseLong( range.substring( pos + 1 ) ); 449 return new HighLowIdSet( lowVal, highVal ); 450 } 451 } 452 catch ( NumberFormatException e ) { 453 throw new ProtocolException( "Invalid message set."); 454 } 455 } 456 457 private long parseLong( String value ) { 458 if ( value.length() == 1 && value.charAt(0) == '*' ) { 459 return Long.MAX_VALUE; 460 } 461 return Long.parseLong( value ); 462 } 463 466 protected interface CharacterValidator 467 { 468 473 boolean isValid( char chr ); 474 } 475 476 protected class NoopCharValidator implements CharacterValidator 477 { 478 public boolean isValid( char chr ) 479 { 480 return true; 481 } 482 } 483 484 protected class ATOM_CHARValidator implements CharacterValidator 485 { 486 public boolean isValid( char chr ) 487 { 488 return ( isCHAR( chr ) && !isAtomSpecial( chr ) && 489 !isListWildcard( chr ) && !isQuotedSpecial( chr ) ); 490 } 491 492 private boolean isAtomSpecial( char chr ) 493 { 494 return ( chr == '(' || 495 chr == ')' || 496 chr == '{' || 497 chr == ' ' || 498 chr == Character.CONTROL ); 499 } 500 } 501 502 protected class DigitCharValidator implements CharacterValidator 503 { 504 public boolean isValid( char chr ) 505 { 506 return ( ( chr >= '0' && chr <= '9' ) || 507 chr == '*' ); 508 } 509 } 510 511 private class TagCharValidator extends ATOM_CHARValidator 512 { 513 public boolean isValid( char chr ) 514 { 515 if ( chr == '+' ) return false; 516 return super.isValid( chr ); 517 } 518 } 519 520 private class MessageSetCharValidator implements CharacterValidator 521 { 522 public boolean isValid( char chr ) 523 { 524 return ( isDigit( chr ) || 525 chr == ':' || 526 chr == '*' || 527 chr == ',' ); 528 } 529 530 private boolean isDigit( char chr ) 531 { 532 return '0' <= chr && chr <= '9'; 533 } 534 } 535 536 private class HighLowIdSet implements IdSet 537 { 538 private long lowVal; 539 private long highVal; 540 541 public HighLowIdSet( long lowVal, long highVal ) 542 { 543 this.lowVal = lowVal; 544 this.highVal = highVal; 545 } 546 547 public boolean includes( long value ) { 548 return ( lowVal <= value ) && ( value <= highVal ); 549 } 550 } 551 552 private class CompoundIdSet implements IdSet 553 { 554 private List idSets = new ArrayList (); 555 556 void addIdSet( IdSet set ) { 557 idSets.add( set ); 558 } 559 560 public boolean includes( long value ) 561 { 562 for ( int i = 0; i < idSets.size(); i++ ) { 563 IdSet idSet = ( IdSet ) idSets.get( i ); 564 if ( idSet.includes( value ) ) { 565 return true; 566 } 567 } 568 return false; 569 } 570 } 571 572 573 } 574 | Popular Tags |