1 /* 2 * Copyright 2011-2016, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "Response.h" 8 9 #include <algorithm> 10 #include <stdlib.h> 11 12 #include <UnicodeChar.h> 13 14 15 #define TRACE_IMAP 16 #ifdef TRACE_IMAP 17 # define TRACE(...) printf(__VA_ARGS__) 18 #else 19 # define TRACE(...) ; 20 #endif 21 22 23 namespace IMAP { 24 25 26 // Note, the following alphabet is a modified base64; the '/' is replaced by 27 // a ',' here. 28 static const char kBase64Alphabet[64] = { 29 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 30 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 31 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 32 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 33 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 34 '+', ',' 35 }; 36 static char kInverseBase64Alphabet[128]; 37 static bool kInverseBase64Initialized = false; 38 39 40 RFC3501Encoding::RFC3501Encoding() 41 { 42 if (!kInverseBase64Initialized) { 43 // This is not thread safe, but it's not harmful 44 for (size_t i = 0; i < sizeof(kBase64Alphabet); i++) 45 kInverseBase64Alphabet[(int)kBase64Alphabet[i]] = i + 1; 46 kInverseBase64Initialized = true; 47 } 48 } 49 50 51 RFC3501Encoding::~RFC3501Encoding() 52 { 53 } 54 55 56 BString 57 RFC3501Encoding::Encode(const BString& clearText) const 58 { 59 const char* clear = clearText.String(); 60 bool shifted = false; 61 int32 bitsToWrite = 0; 62 int32 sextet = 0; 63 BString buffer; 64 65 while (true) { 66 uint32 c = BUnicodeChar::FromUTF8(&clear); 67 if (c == 0) 68 break; 69 70 if (!shifted && c == '&') 71 buffer += "&-"; 72 else if (c >= 0x20 && c <= 0x7e) { 73 _Unshift(buffer, bitsToWrite, sextet, shifted); 74 buffer += c; 75 } else { 76 // Enter shifted mode, encode in base64 77 if (!shifted) { 78 buffer += '&'; 79 shifted = true; 80 } 81 82 bitsToWrite += 16; 83 while (bitsToWrite >= 6) { 84 bitsToWrite -= 6; 85 buffer += kBase64Alphabet[(sextet + (c >> bitsToWrite)) & 0x3f]; 86 sextet = 0; 87 } 88 sextet = (c << (6 - bitsToWrite)) & 0x3f; 89 } 90 } 91 92 _Unshift(buffer, bitsToWrite, sextet, shifted); 93 return buffer; 94 } 95 96 97 BString 98 RFC3501Encoding::Decode(const BString& encodedText) const 99 { 100 int32 end = encodedText.Length(); 101 BString buffer; 102 for (int32 i = 0; i < end; i++) { 103 uint8 c = (uint8)encodedText.ByteAt(i); 104 if (c == '&') { 105 if (i < end - 1 && encodedText.ByteAt(i + 1) == '-') { 106 // just add an ampersand 107 buffer += '&'; 108 i++; 109 } else { 110 // base64 encoded chunk 111 uint32 value = 0; 112 int32 bitsRead = 0; 113 while (true) { 114 if (++i >= end) 115 throw ParseException("Malformed base64!"); 116 117 c = encodedText.ByteAt(i); 118 if (c == '-') { 119 if (value != 0 || bitsRead >= 6) 120 throw ParseException("Base64 encoding ends early!"); 121 break; 122 } 123 if (c >= 128) 124 throw ParseException("Malformed base64!"); 125 int32 sextet = kInverseBase64Alphabet[c] - 1; 126 if (sextet >= 0) { 127 bitsRead += 6; 128 if (bitsRead < 16) { 129 value += sextet << (16 - bitsRead); 130 } else { 131 bitsRead -= 16; 132 value += sextet >> bitsRead; 133 _ToUTF8(buffer, value); 134 135 // Move on to next character 136 value = (sextet << (16 - bitsRead)) & 0xffff; 137 } 138 } else { 139 buffer += c; 140 if (value != 0 || bitsRead >= 6) 141 throw ParseException("Malformed base64!"); 142 break; 143 } 144 } 145 } 146 } else 147 buffer += c; 148 } 149 return buffer; 150 } 151 152 153 void 154 RFC3501Encoding::_ToUTF8(BString& string, uint32 c) const 155 { 156 if (c < 0x80) 157 string += (char)c; 158 else if (c < 0x800) { 159 string += 0xc0 | (c >> 6); 160 string += 0x80 | (c & 0x3f); 161 } else if (c < 0x10000) { 162 string += 0xe0 | (c >> 12); 163 string += 0x80 | ((c >> 6) & 0x3f); 164 string += 0x80 | (c & 0x3f); 165 } else if (c <= 0x10ffff) { 166 string += 0xf0 | (c >> 18); 167 string += 0x80 | ((c >> 12) & 0x3f); 168 string += 0x80 | ((c >> 6) & 0x3f); 169 string += 0x80 | (c & 0x3f); 170 } 171 } 172 173 174 //! Exit base64, or "shifted" mode. 175 void 176 RFC3501Encoding::_Unshift(BString& buffer, int32& bitsToWrite, int32& sextet, 177 bool& shifted) const 178 { 179 if (!shifted) 180 return; 181 182 if (bitsToWrite != 0) 183 buffer += kBase64Alphabet[sextet]; 184 buffer += '-'; 185 sextet = 0; 186 bitsToWrite = 0; 187 shifted = false; 188 } 189 190 191 // #pragma mark - 192 193 194 ArgumentList::ArgumentList() 195 : 196 BObjectList<Argument>(5, true) 197 { 198 } 199 200 201 ArgumentList::~ArgumentList() 202 { 203 } 204 205 206 bool 207 ArgumentList::Contains(const char* string) const 208 { 209 for (int32 i = 0; i < CountItems(); i++) { 210 if (StringArgument* argument 211 = dynamic_cast<StringArgument*>(ItemAt(i))) { 212 if (argument->String().ICompare(string) == 0) 213 return true; 214 } 215 } 216 return false; 217 } 218 219 220 BString 221 ArgumentList::StringAt(int32 index) const 222 { 223 if (index >= 0 && index < CountItems()) { 224 if (StringArgument* argument 225 = dynamic_cast<StringArgument*>(ItemAt(index))) 226 return argument->String(); 227 } 228 return ""; 229 } 230 231 232 bool 233 ArgumentList::IsStringAt(int32 index) const 234 { 235 if (index >= 0 && index < CountItems()) { 236 if (dynamic_cast<StringArgument*>(ItemAt(index)) != NULL) 237 return true; 238 } 239 return false; 240 } 241 242 243 bool 244 ArgumentList::EqualsAt(int32 index, const char* string) const 245 { 246 return StringAt(index).ICompare(string) == 0; 247 } 248 249 250 ArgumentList& 251 ArgumentList::ListAt(int32 index) const 252 { 253 if (index >= 0 && index < CountItems()) { 254 if (ListArgument* argument = dynamic_cast<ListArgument*>(ItemAt(index))) 255 return argument->List(); 256 } 257 258 static ArgumentList empty; 259 return empty; 260 } 261 262 263 bool 264 ArgumentList::IsListAt(int32 index) const 265 { 266 return index >= 0 && index < CountItems() 267 && dynamic_cast<ListArgument*>(ItemAt(index)) != NULL; 268 } 269 270 271 bool 272 ArgumentList::IsListAt(int32 index, char kind) const 273 { 274 if (index >= 0 && index < CountItems()) { 275 if (ListArgument* argument = dynamic_cast<ListArgument*>(ItemAt(index))) 276 return argument->Kind() == kind; 277 } 278 return false; 279 } 280 281 282 uint32 283 ArgumentList::NumberAt(int32 index) const 284 { 285 return atoul(StringAt(index).String()); 286 } 287 288 289 bool 290 ArgumentList::IsNumberAt(int32 index) const 291 { 292 BString string = StringAt(index); 293 for (int32 i = 0; i < string.Length(); i++) { 294 if (!isdigit(string.ByteAt(i))) 295 return false; 296 } 297 return string.Length() > 0; 298 } 299 300 301 BString 302 ArgumentList::ToString() const 303 { 304 BString string; 305 306 for (int32 i = 0; i < CountItems(); i++) { 307 if (i > 0) 308 string += ", "; 309 string += ItemAt(i)->ToString(); 310 } 311 return string; 312 } 313 314 315 // #pragma mark - 316 317 318 Argument::Argument() 319 { 320 } 321 322 323 Argument::~Argument() 324 { 325 } 326 327 328 // #pragma mark - 329 330 331 ListArgument::ListArgument(char kind) 332 : 333 fKind(kind) 334 { 335 } 336 337 338 BString 339 ListArgument::ToString() const 340 { 341 BString string(fKind == '[' ? "[" : "("); 342 string += fList.ToString(); 343 string += fKind == '[' ? "]" : ")"; 344 345 return string; 346 } 347 348 349 // #pragma mark - 350 351 352 StringArgument::StringArgument(const BString& string) 353 : 354 fString(string) 355 { 356 } 357 358 359 StringArgument::StringArgument(const StringArgument& other) 360 : 361 fString(other.fString) 362 { 363 } 364 365 366 BString 367 StringArgument::ToString() const 368 { 369 return fString; 370 } 371 372 373 // #pragma mark - 374 375 376 ParseException::ParseException() 377 { 378 fBuffer[0] = '\0'; 379 } 380 381 382 ParseException::ParseException(const char* format, ...) 383 { 384 va_list args; 385 va_start(args, format); 386 vsnprintf(fBuffer, sizeof(fBuffer), format, args); 387 va_end(args); 388 } 389 390 391 // #pragma mark - 392 393 394 StreamException::StreamException(status_t status) 395 : 396 fStatus(status) 397 { 398 } 399 400 401 // #pragma mark - 402 403 404 ExpectedParseException::ExpectedParseException(char expected, char instead) 405 { 406 char bufferA[8]; 407 char bufferB[8]; 408 snprintf(fBuffer, sizeof(fBuffer), "Expected %s, but got %s instead!", 409 CharToString(bufferA, sizeof(bufferA), expected), 410 CharToString(bufferB, sizeof(bufferB), instead)); 411 } 412 413 414 const char* 415 ExpectedParseException::CharToString(char* buffer, size_t size, char c) 416 { 417 snprintf(buffer, size, isprint(c) ? "\"%c\"" : "(%x)", c); 418 return buffer; 419 } 420 421 422 // #pragma mark - 423 424 425 LiteralHandler::LiteralHandler() 426 { 427 } 428 429 430 LiteralHandler::~LiteralHandler() 431 { 432 } 433 434 435 // #pragma mark - 436 437 438 Response::Response() 439 : 440 fTag(0), 441 fContinuation(false), 442 fHasNextChar(false) 443 { 444 } 445 446 447 Response::~Response() 448 { 449 } 450 451 452 void 453 Response::Parse(BDataIO& stream, LiteralHandler* handler) throw(ParseException) 454 { 455 MakeEmpty(); 456 fLiteralHandler = handler; 457 fTag = 0; 458 fContinuation = false; 459 fHasNextChar = false; 460 461 char begin = Next(stream); 462 if (begin == '*') { 463 // Untagged response 464 Consume(stream, ' '); 465 } else if (begin == '+') { 466 // Continuation 467 fContinuation = true; 468 } else if (begin == 'A') { 469 // Tagged response 470 fTag = ExtractNumber(stream); 471 Consume(stream, ' '); 472 } else 473 throw ParseException("Unexpected response begin"); 474 475 char c = ParseLine(*this, stream); 476 if (c != '\0') 477 throw ExpectedParseException('\0', c); 478 } 479 480 481 bool 482 Response::IsCommand(const char* command) const 483 { 484 return IsUntagged() && EqualsAt(0, command); 485 } 486 487 488 char 489 Response::ParseLine(ArgumentList& arguments, BDataIO& stream) 490 { 491 while (true) { 492 char c = Peek(stream); 493 if (c == '\0') 494 break; 495 496 switch (c) { 497 case '(': 498 ParseList(arguments, stream, '(', ')'); 499 break; 500 case '[': 501 ParseList(arguments, stream, '[', ']'); 502 break; 503 case ')': 504 case ']': 505 Consume(stream, c); 506 return c; 507 case '"': 508 ParseQuoted(arguments, stream); 509 break; 510 case '{': 511 ParseLiteral(arguments, stream); 512 break; 513 514 case ' ': 515 case '\t': 516 // whitespace 517 Consume(stream, c); 518 break; 519 520 case '\r': 521 Consume(stream, '\r'); 522 Consume(stream, '\n'); 523 return '\0'; 524 case '\n': 525 Consume(stream, '\n'); 526 return '\0'; 527 528 default: 529 ParseString(arguments, stream); 530 break; 531 } 532 } 533 534 return '\0'; 535 } 536 537 538 void 539 Response::ParseList(ArgumentList& arguments, BDataIO& stream, char start, 540 char end) 541 { 542 Consume(stream, start); 543 544 ListArgument* argument = new ListArgument(start); 545 arguments.AddItem(argument); 546 547 char c = ParseLine(argument->List(), stream); 548 if (c != end) 549 throw ExpectedParseException(end, c); 550 } 551 552 553 void 554 Response::ParseQuoted(ArgumentList& arguments, BDataIO& stream) 555 { 556 Consume(stream, '"'); 557 558 BString string; 559 while (true) { 560 char c = Next(stream); 561 if (c == '\\') { 562 c = Next(stream); 563 } else if (c == '"') { 564 arguments.AddItem(new StringArgument(string)); 565 return; 566 } 567 if (c == '\0') 568 break; 569 570 string += c; 571 } 572 573 throw ParseException("Unexpected end of qouted string!"); 574 } 575 576 577 void 578 Response::ParseLiteral(ArgumentList& arguments, BDataIO& stream) 579 { 580 Consume(stream, '{'); 581 size_t size = ExtractNumber(stream); 582 Consume(stream, '}'); 583 Consume(stream, '\r'); 584 Consume(stream, '\n'); 585 586 bool handled = false; 587 if (fLiteralHandler != NULL) { 588 handled = fLiteralHandler->HandleLiteral(*this, arguments, stream, 589 size); 590 } 591 592 if (!handled && size <= 65536) { 593 // The default implementation just adds the data as a string 594 TRACE("Trying to read literal with %" B_PRIuSIZE " bytes.\n", size); 595 BString string; 596 char* buffer = string.LockBuffer(size); 597 if (buffer == NULL) { 598 throw ParseException("Not enough memory for literal of %" 599 B_PRIuSIZE " bytes.", size); 600 } 601 602 size_t totalRead = 0; 603 while (totalRead < size) { 604 ssize_t bytesRead = stream.Read(buffer + totalRead, 605 size - totalRead); 606 if (bytesRead == 0) 607 throw ParseException("Unexpected end of literal"); 608 if (bytesRead < 0) 609 throw StreamException(bytesRead); 610 611 totalRead += bytesRead; 612 } 613 614 string.UnlockBuffer(size); 615 arguments.AddItem(new StringArgument(string)); 616 } else { 617 // Skip any bytes left in the literal stream 618 _SkipLiteral(stream, size); 619 } 620 } 621 622 623 void 624 Response::ParseString(ArgumentList& arguments, BDataIO& stream) 625 { 626 arguments.AddItem(new StringArgument(ExtractString(stream))); 627 } 628 629 630 BString 631 Response::ExtractString(BDataIO& stream) 632 { 633 BString string; 634 635 // TODO: parse modified UTF-7 as described in RFC 3501, 5.1.3 636 while (true) { 637 char c = Peek(stream); 638 if (c == '\0') 639 break; 640 if (c <= ' ' || strchr("()[]{}\"", c) != NULL) 641 return string; 642 643 string += Next(stream); 644 } 645 646 throw ParseException("Unexpected end of string"); 647 } 648 649 650 size_t 651 Response::ExtractNumber(BDataIO& stream) 652 { 653 BString string = ExtractString(stream); 654 655 const char* end; 656 size_t number = strtoul(string.String(), (char**)&end, 10); 657 if (end == NULL || end[0] != '\0') 658 ParseException("Invalid number!"); 659 660 return number; 661 } 662 663 664 void 665 Response::Consume(BDataIO& stream, char expected) 666 { 667 char c = Next(stream); 668 if (c != expected) 669 throw ExpectedParseException(expected, c); 670 } 671 672 673 char 674 Response::Next(BDataIO& stream) 675 { 676 if (fHasNextChar) { 677 fHasNextChar = false; 678 return fNextChar; 679 } 680 681 return Read(stream); 682 } 683 684 685 char 686 Response::Peek(BDataIO& stream) 687 { 688 if (fHasNextChar) 689 return fNextChar; 690 691 fNextChar = Read(stream); 692 fHasNextChar = true; 693 694 return fNextChar; 695 } 696 697 698 char 699 Response::Read(BDataIO& stream) 700 { 701 char c; 702 ssize_t bytesRead = stream.Read(&c, 1); 703 if (bytesRead == 1) { 704 printf("%c", c); 705 return c; 706 } 707 708 if (bytesRead == 0) 709 throw ParseException("Unexpected end of stream"); 710 711 throw StreamException(bytesRead); 712 } 713 714 715 void 716 Response::_SkipLiteral(BDataIO& stream, size_t size) 717 { 718 char buffer[4096]; 719 size_t totalRead = 0; 720 while (totalRead < size) { 721 size_t toRead = std::min(sizeof(buffer), size - totalRead); 722 ssize_t bytesRead = stream.Read(buffer, toRead); 723 if (bytesRead == 0) 724 throw ParseException("Unexpected end of literal"); 725 if (bytesRead < 0) 726 throw StreamException(bytesRead); 727 728 totalRead += bytesRead; 729 } 730 } 731 732 733 // #pragma mark - 734 735 736 ResponseParser::ResponseParser(BDataIO& stream) 737 : 738 fLiteralHandler(NULL) 739 { 740 SetTo(stream); 741 } 742 743 744 ResponseParser::~ResponseParser() 745 { 746 } 747 748 749 void 750 ResponseParser::SetTo(BDataIO& stream) 751 { 752 fStream = &stream; 753 } 754 755 756 void 757 ResponseParser::SetLiteralHandler(LiteralHandler* handler) 758 { 759 fLiteralHandler = handler; 760 } 761 762 763 status_t 764 ResponseParser::NextResponse(Response& response, bigtime_t timeout) 765 throw(ParseException) 766 { 767 response.Parse(*fStream, fLiteralHandler); 768 return B_OK; 769 } 770 771 772 } // namespace IMAP 773