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