1 /* 2 * Copyright 2010-2013 Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Christophe Huriaux, c.huriaux@gmail.com 7 */ 8 9 10 #include <HttpForm.h> 11 12 #include <cstdlib> 13 #include <cstring> 14 #include <ctime> 15 16 #include <File.h> 17 #include <NodeInfo.h> 18 #include <TypeConstants.h> 19 #include <Url.h> 20 21 22 static int32 kBoundaryRandomSize = 16; 23 24 using namespace std; 25 using namespace BPrivate::Network; 26 27 28 // #pragma mark - BHttpFormData 29 30 31 BHttpFormData::BHttpFormData() 32 : 33 fDataType(B_HTTPFORM_STRING), 34 fCopiedBuffer(false), 35 fFileMark(false), 36 fBufferValue(NULL), 37 fBufferSize(0) 38 { 39 } 40 41 42 BHttpFormData::BHttpFormData(const BString& name, const BString& value) 43 : 44 fDataType(B_HTTPFORM_STRING), 45 fCopiedBuffer(false), 46 fFileMark(false), 47 fName(name), 48 fStringValue(value), 49 fBufferValue(NULL), 50 fBufferSize(0) 51 { 52 } 53 54 55 BHttpFormData::BHttpFormData(const BString& name, const BPath& file) 56 : 57 fDataType(B_HTTPFORM_FILE), 58 fCopiedBuffer(false), 59 fFileMark(false), 60 fName(name), 61 fPathValue(file), 62 fBufferValue(NULL), 63 fBufferSize(0) 64 { 65 } 66 67 68 BHttpFormData::BHttpFormData(const BString& name, const void* buffer, 69 ssize_t size) 70 : 71 fDataType(B_HTTPFORM_BUFFER), 72 fCopiedBuffer(false), 73 fFileMark(false), 74 fName(name), 75 fBufferValue(buffer), 76 fBufferSize(size) 77 { 78 } 79 80 81 BHttpFormData::BHttpFormData(const BHttpFormData& other) 82 : 83 fCopiedBuffer(false), 84 fFileMark(false), 85 fBufferValue(NULL), 86 fBufferSize(0) 87 { 88 *this = other; 89 } 90 91 92 BHttpFormData::~BHttpFormData() 93 { 94 if (fCopiedBuffer) 95 delete[] reinterpret_cast<const char*>(fBufferValue); 96 } 97 98 99 // #pragma mark - Retrieve data informations 100 101 102 bool 103 BHttpFormData::InitCheck() const 104 { 105 if (fDataType == B_HTTPFORM_BUFFER) 106 return fBufferValue != NULL; 107 108 return true; 109 } 110 111 112 const BString& 113 BHttpFormData::Name() const 114 { 115 return fName; 116 } 117 118 119 const BString& 120 BHttpFormData::String() const 121 { 122 return fStringValue; 123 } 124 125 126 const BPath& 127 BHttpFormData::File() const 128 { 129 return fPathValue; 130 } 131 132 133 const void* 134 BHttpFormData::Buffer() const 135 { 136 return fBufferValue; 137 } 138 139 140 ssize_t 141 BHttpFormData::BufferSize() const 142 { 143 return fBufferSize; 144 } 145 146 147 bool 148 BHttpFormData::IsFile() const 149 { 150 return fFileMark; 151 } 152 153 154 const BString& 155 BHttpFormData::Filename() const 156 { 157 return fFilename; 158 } 159 160 161 const BString& 162 BHttpFormData::MimeType() const 163 { 164 return fMimeType; 165 } 166 167 168 form_content_type 169 BHttpFormData::Type() const 170 { 171 return fDataType; 172 } 173 174 175 // #pragma mark - Change behavior 176 177 178 status_t 179 BHttpFormData::MarkAsFile(const BString& filename, const BString& mimeType) 180 { 181 if (fDataType == B_HTTPFORM_UNKNOWN || fDataType == B_HTTPFORM_FILE) 182 return B_ERROR; 183 184 fFilename = filename; 185 fMimeType = mimeType; 186 fFileMark = true; 187 188 return B_OK; 189 } 190 191 192 void 193 BHttpFormData::UnmarkAsFile() 194 { 195 fFilename.Truncate(0, true); 196 fMimeType.Truncate(0, true); 197 fFileMark = false; 198 } 199 200 201 status_t 202 BHttpFormData::CopyBuffer() 203 { 204 if (fDataType != B_HTTPFORM_BUFFER) 205 return B_ERROR; 206 207 char* copiedBuffer = new(std::nothrow) char[fBufferSize]; 208 if (copiedBuffer == NULL) 209 return B_NO_MEMORY; 210 211 memcpy(copiedBuffer, fBufferValue, fBufferSize); 212 fBufferValue = copiedBuffer; 213 fCopiedBuffer = true; 214 215 return B_OK; 216 } 217 218 219 BHttpFormData& 220 BHttpFormData::operator=(const BHttpFormData& other) 221 { 222 fDataType = other.fDataType; 223 fCopiedBuffer = false; 224 fFileMark = other.fFileMark; 225 fName = other.fName; 226 fStringValue = other.fStringValue; 227 fPathValue = other.fPathValue; 228 fBufferValue = other.fBufferValue; 229 fBufferSize = other.fBufferSize; 230 fFilename = other.fFilename; 231 fMimeType = other.fMimeType; 232 233 if (other.fCopiedBuffer) 234 CopyBuffer(); 235 236 return *this; 237 } 238 239 240 // #pragma mark - BHttpForm 241 242 243 BHttpForm::BHttpForm() 244 : 245 fType(B_HTTP_FORM_URL_ENCODED) 246 { 247 } 248 249 250 BHttpForm::BHttpForm(const BHttpForm& other) 251 : 252 fFields(other.fFields), 253 fType(other.fType), 254 fMultipartBoundary(other.fMultipartBoundary) 255 { 256 } 257 258 259 BHttpForm::BHttpForm(const BString& formString) 260 : 261 fType(B_HTTP_FORM_URL_ENCODED) 262 { 263 ParseString(formString); 264 } 265 266 267 BHttpForm::~BHttpForm() 268 { 269 Clear(); 270 } 271 272 273 // #pragma mark - Form string parsing 274 275 276 void 277 BHttpForm::ParseString(const BString& formString) 278 { 279 int32 index = 0; 280 281 while (index < formString.Length()) 282 _ExtractNameValuePair(formString, &index); 283 } 284 285 286 BString 287 BHttpForm::RawData() const 288 { 289 BString result; 290 291 if (fType == B_HTTP_FORM_URL_ENCODED) { 292 for (FormStorage::const_iterator it = fFields.begin(); 293 it != fFields.end(); it++) { 294 const BHttpFormData* currentField = &it->second; 295 296 switch (currentField->Type()) { 297 case B_HTTPFORM_UNKNOWN: 298 break; 299 300 case B_HTTPFORM_STRING: 301 result << '&' << BUrl::UrlEncode(currentField->Name()) 302 << '=' << BUrl::UrlEncode(currentField->String()); 303 break; 304 305 case B_HTTPFORM_FILE: 306 break; 307 308 case B_HTTPFORM_BUFFER: 309 // Send the buffer only if its not marked as a file 310 if (!currentField->IsFile()) { 311 result << '&' << BUrl::UrlEncode(currentField->Name()) 312 << '='; 313 result.Append( 314 reinterpret_cast<const char*>(currentField->Buffer()), 315 currentField->BufferSize()); 316 } 317 break; 318 } 319 } 320 321 result.Remove(0, 1); 322 } else if (fType == B_HTTP_FORM_MULTIPART) { 323 // Very slow and memory consuming method since we're caching the 324 // file content, this should be preferably handled by the protocol 325 for (FormStorage::const_iterator it = fFields.begin(); 326 it != fFields.end(); it++) { 327 const BHttpFormData* currentField = &it->second; 328 result << _GetMultipartHeader(currentField); 329 330 switch (currentField->Type()) { 331 case B_HTTPFORM_UNKNOWN: 332 break; 333 334 case B_HTTPFORM_STRING: 335 result << currentField->String(); 336 break; 337 338 case B_HTTPFORM_FILE: 339 { 340 BFile upFile(currentField->File().Path(), B_READ_ONLY); 341 char readBuffer[1024]; 342 ssize_t readSize; 343 344 readSize = upFile.Read(readBuffer, 1024); 345 346 while (readSize > 0) { 347 result.Append(readBuffer, readSize); 348 readSize = upFile.Read(readBuffer, 1024); 349 } 350 break; 351 } 352 353 case B_HTTPFORM_BUFFER: 354 result.Append( 355 reinterpret_cast<const char*>(currentField->Buffer()), 356 currentField->BufferSize()); 357 break; 358 } 359 360 result << "\r\n"; 361 } 362 363 result << "--" << fMultipartBoundary << "--\r\n"; 364 } 365 366 return result; 367 } 368 369 370 // #pragma mark - Form add 371 372 373 status_t 374 BHttpForm::AddString(const BString& fieldName, const BString& value) 375 { 376 BHttpFormData formData(fieldName, value); 377 if (!formData.InitCheck()) 378 return B_ERROR; 379 380 fFields.insert(pair<BString, BHttpFormData>(fieldName, formData)); 381 return B_OK; 382 } 383 384 385 status_t 386 BHttpForm::AddInt(const BString& fieldName, int32 value) 387 { 388 BString strValue; 389 strValue << value; 390 391 return AddString(fieldName, strValue); 392 } 393 394 395 status_t 396 BHttpForm::AddFile(const BString& fieldName, const BPath& file) 397 { 398 BHttpFormData formData(fieldName, file); 399 if (!formData.InitCheck()) 400 return B_ERROR; 401 402 fFields.insert(pair<BString, BHttpFormData>(fieldName, formData)); 403 404 if (fType != B_HTTP_FORM_MULTIPART) 405 SetFormType(B_HTTP_FORM_MULTIPART); 406 return B_OK; 407 } 408 409 410 status_t 411 BHttpForm::AddBuffer(const BString& fieldName, const void* buffer, 412 ssize_t size) 413 { 414 BHttpFormData formData(fieldName, buffer, size); 415 if (!formData.InitCheck()) 416 return B_ERROR; 417 418 fFields.insert(pair<BString, BHttpFormData>(fieldName, formData)); 419 return B_OK; 420 } 421 422 423 status_t 424 BHttpForm::AddBufferCopy(const BString& fieldName, const void* buffer, 425 ssize_t size) 426 { 427 BHttpFormData formData(fieldName, buffer, size); 428 if (!formData.InitCheck()) 429 return B_ERROR; 430 431 // Copy the buffer of the inserted form data copy to 432 // avoid an unneeded copy of the buffer upon insertion 433 pair<FormStorage::iterator, bool> insertResult 434 = fFields.insert(pair<BString, BHttpFormData>(fieldName, formData)); 435 436 return insertResult.first->second.CopyBuffer(); 437 } 438 439 440 // #pragma mark - Mark a field as a filename 441 442 443 void 444 BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename, 445 const BString& mimeType) 446 { 447 FormStorage::iterator it = fFields.find(fieldName); 448 449 if (it == fFields.end()) 450 return; 451 452 it->second.MarkAsFile(filename, mimeType); 453 if (fType != B_HTTP_FORM_MULTIPART) 454 SetFormType(B_HTTP_FORM_MULTIPART); 455 } 456 457 458 void 459 BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename) 460 { 461 MarkAsFile(fieldName, filename, ""); 462 } 463 464 465 void 466 BHttpForm::UnmarkAsFile(const BString& fieldName) 467 { 468 FormStorage::iterator it = fFields.find(fieldName); 469 470 if (it == fFields.end()) 471 return; 472 473 it->second.UnmarkAsFile(); 474 } 475 476 477 // #pragma mark - Change form type 478 479 480 void 481 BHttpForm::SetFormType(form_type type) 482 { 483 fType = type; 484 485 if (fType == B_HTTP_FORM_MULTIPART) 486 _GenerateMultipartBoundary(); 487 } 488 489 490 // #pragma mark - Form test 491 492 493 bool 494 BHttpForm::HasField(const BString& name) const 495 { 496 return (fFields.find(name) != fFields.end()); 497 } 498 499 500 // #pragma mark - Form retrieve 501 502 503 BString 504 BHttpForm::GetMultipartHeader(const BString& fieldName) const 505 { 506 FormStorage::const_iterator it = fFields.find(fieldName); 507 508 if (it == fFields.end()) 509 return BString(""); 510 511 return _GetMultipartHeader(&it->second); 512 } 513 514 515 form_type 516 BHttpForm::GetFormType() const 517 { 518 return fType; 519 } 520 521 522 const BString& 523 BHttpForm::GetMultipartBoundary() const 524 { 525 return fMultipartBoundary; 526 } 527 528 529 BString 530 BHttpForm::GetMultipartFooter() const 531 { 532 BString result = "--"; 533 result << fMultipartBoundary << "--\r\n"; 534 return result; 535 } 536 537 538 ssize_t 539 BHttpForm::ContentLength() const 540 { 541 if (fType == B_HTTP_FORM_URL_ENCODED) 542 return RawData().Length(); 543 544 ssize_t contentLength = 0; 545 546 for (FormStorage::const_iterator it = fFields.begin(); 547 it != fFields.end(); it++) { 548 const BHttpFormData* c = &it->second; 549 contentLength += _GetMultipartHeader(c).Length(); 550 551 switch (c->Type()) { 552 case B_HTTPFORM_UNKNOWN: 553 break; 554 555 case B_HTTPFORM_STRING: 556 contentLength += c->String().Length(); 557 break; 558 559 case B_HTTPFORM_FILE: 560 { 561 BFile upFile(c->File().Path(), B_READ_ONLY); 562 upFile.Seek(0, SEEK_END); 563 contentLength += upFile.Position(); 564 break; 565 } 566 567 case B_HTTPFORM_BUFFER: 568 contentLength += c->BufferSize(); 569 break; 570 } 571 572 contentLength += 2; 573 } 574 575 contentLength += fMultipartBoundary.Length() + 6; 576 577 return contentLength; 578 } 579 580 581 // #pragma mark Form iterator 582 583 584 BHttpForm::Iterator 585 BHttpForm::GetIterator() 586 { 587 return BHttpForm::Iterator(this); 588 } 589 590 591 // #pragma mark - Form clear 592 593 594 void 595 BHttpForm::Clear() 596 { 597 fFields.clear(); 598 } 599 600 601 // #pragma mark - Overloaded operators 602 603 604 BHttpFormData& 605 BHttpForm::operator[](const BString& name) 606 { 607 if (!HasField(name)) 608 AddString(name, ""); 609 610 return fFields[name]; 611 } 612 613 614 void 615 BHttpForm::_ExtractNameValuePair(const BString& formString, int32* index) 616 { 617 // Look for a name=value pair 618 int16 firstAmpersand = formString.FindFirst("&", *index); 619 int16 firstEqual = formString.FindFirst("=", *index); 620 621 BString name; 622 BString value; 623 624 if (firstAmpersand == -1) { 625 if (firstEqual != -1) { 626 formString.CopyInto(name, *index, firstEqual - *index); 627 formString.CopyInto(value, firstEqual + 1, 628 formString.Length() - firstEqual - 1); 629 } else 630 formString.CopyInto(value, *index, 631 formString.Length() - *index); 632 633 *index = formString.Length() + 1; 634 } else { 635 if (firstEqual != -1 && firstEqual < firstAmpersand) { 636 formString.CopyInto(name, *index, firstEqual - *index); 637 formString.CopyInto(value, firstEqual + 1, 638 firstAmpersand - firstEqual - 1); 639 } else 640 formString.CopyInto(value, *index, firstAmpersand - *index); 641 642 *index = firstAmpersand + 1; 643 } 644 645 AddString(name, value); 646 } 647 648 649 void 650 BHttpForm::_GenerateMultipartBoundary() 651 { 652 fMultipartBoundary = "----------------------------"; 653 654 srand(time(NULL)); 655 // TODO: Maybe a more robust way to seed the random number 656 // generator is needed? 657 658 for (int32 i = 0; i < kBoundaryRandomSize; i++) 659 fMultipartBoundary << (char)(rand() % 10 + '0'); 660 } 661 662 663 // #pragma mark - Field information access by std iterator 664 665 666 BString 667 BHttpForm::_GetMultipartHeader(const BHttpFormData* element) const 668 { 669 BString result; 670 result << "--" << fMultipartBoundary << "\r\n"; 671 result << "Content-Disposition: form-data; name=\"" << element->Name() 672 << '"'; 673 674 switch (element->Type()) { 675 case B_HTTPFORM_UNKNOWN: 676 break; 677 678 case B_HTTPFORM_FILE: 679 { 680 result << "; filename=\"" << element->File().Leaf() << '"'; 681 682 BNode fileNode(element->File().Path()); 683 BNodeInfo fileInfo(&fileNode); 684 685 result << "\r\nContent-Type: "; 686 char tempMime[128]; 687 if (fileInfo.GetType(tempMime) == B_OK) 688 result << tempMime; 689 else 690 result << "application/octet-stream"; 691 692 break; 693 } 694 695 case B_HTTPFORM_STRING: 696 case B_HTTPFORM_BUFFER: 697 if (element->IsFile()) { 698 result << "; filename=\"" << element->Filename() << '"'; 699 700 if (element->MimeType().Length() > 0) 701 result << "\r\nContent-Type: " << element->MimeType(); 702 else 703 result << "\r\nContent-Type: text/plain"; 704 } 705 break; 706 } 707 708 result << "\r\n\r\n"; 709 710 return result; 711 } 712 713 714 // #pragma mark - Iterator 715 716 717 BHttpForm::Iterator::Iterator(BHttpForm* form) 718 : 719 fElement(NULL) 720 { 721 fForm = form; 722 fStdIterator = form->fFields.begin(); 723 _FindNext(); 724 } 725 726 727 BHttpForm::Iterator::Iterator(const Iterator& other) 728 { 729 *this = other; 730 } 731 732 733 bool 734 BHttpForm::Iterator::HasNext() const 735 { 736 return fStdIterator != fForm->fFields.end(); 737 } 738 739 740 BHttpFormData* 741 BHttpForm::Iterator::Next() 742 { 743 BHttpFormData* element = fElement; 744 _FindNext(); 745 return element; 746 } 747 748 749 void 750 BHttpForm::Iterator::Remove() 751 { 752 fForm->fFields.erase(fStdIterator); 753 fElement = NULL; 754 } 755 756 757 BString 758 BHttpForm::Iterator::MultipartHeader() 759 { 760 return fForm->_GetMultipartHeader(fPrevElement); 761 } 762 763 764 BHttpForm::Iterator& 765 BHttpForm::Iterator::operator=(const Iterator& other) 766 { 767 fForm = other.fForm; 768 fStdIterator = other.fStdIterator; 769 fElement = other.fElement; 770 fPrevElement = other.fPrevElement; 771 772 return *this; 773 } 774 775 776 void 777 BHttpForm::Iterator::_FindNext() 778 { 779 fPrevElement = fElement; 780 781 if (fStdIterator != fForm->fFields.end()) { 782 fElement = &fStdIterator->second; 783 fStdIterator++; 784 } else 785 fElement = NULL; 786 } 787