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 22static int32 kBoundaryRandomSize = 16; 23 24using namespace std; 25using namespace BPrivate::Network; 26 27 28// #pragma mark - BHttpFormData 29 30 31BHttpFormData::BHttpFormData() 32 : 33 fDataType(B_HTTPFORM_STRING), 34 fCopiedBuffer(false), 35 fFileMark(false), 36 fBufferValue(NULL), 37 fBufferSize(0) 38{ 39} 40 41 42BHttpFormData::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 55BHttpFormData::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 68BHttpFormData::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 81BHttpFormData::BHttpFormData(const BHttpFormData& other) 82 : 83 fCopiedBuffer(false), 84 fFileMark(false), 85 fBufferValue(NULL), 86 fBufferSize(0) 87{ 88 *this = other; 89} 90 91 92BHttpFormData::~BHttpFormData() 93{ 94 if (fCopiedBuffer) 95 delete[] reinterpret_cast<const char*>(fBufferValue); 96} 97 98 99// #pragma mark - Retrieve data informations 100 101 102bool 103BHttpFormData::InitCheck() const 104{ 105 if (fDataType == B_HTTPFORM_BUFFER) 106 return fBufferValue != NULL; 107 108 return true; 109} 110 111 112const BString& 113BHttpFormData::Name() const 114{ 115 return fName; 116} 117 118 119const BString& 120BHttpFormData::String() const 121{ 122 return fStringValue; 123} 124 125 126const BPath& 127BHttpFormData::File() const 128{ 129 return fPathValue; 130} 131 132 133const void* 134BHttpFormData::Buffer() const 135{ 136 return fBufferValue; 137} 138 139 140ssize_t 141BHttpFormData::BufferSize() const 142{ 143 return fBufferSize; 144} 145 146 147bool 148BHttpFormData::IsFile() const 149{ 150 return fFileMark; 151} 152 153 154const BString& 155BHttpFormData::Filename() const 156{ 157 return fFilename; 158} 159 160 161const BString& 162BHttpFormData::MimeType() const 163{ 164 return fMimeType; 165} 166 167 168form_content_type 169BHttpFormData::Type() const 170{ 171 return fDataType; 172} 173 174 175// #pragma mark - Change behavior 176 177 178status_t 179BHttpFormData::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 192void 193BHttpFormData::UnmarkAsFile() 194{ 195 fFilename.Truncate(0, true); 196 fMimeType.Truncate(0, true); 197 fFileMark = false; 198} 199 200 201status_t 202BHttpFormData::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 219BHttpFormData& 220BHttpFormData::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 243BHttpForm::BHttpForm() 244 : 245 fType(B_HTTP_FORM_URL_ENCODED) 246{ 247} 248 249 250BHttpForm::BHttpForm(const BHttpForm& other) 251 : 252 fFields(other.fFields), 253 fType(other.fType), 254 fMultipartBoundary(other.fMultipartBoundary) 255{ 256} 257 258 259BHttpForm::BHttpForm(const BString& formString) 260 : 261 fType(B_HTTP_FORM_URL_ENCODED) 262{ 263 ParseString(formString); 264} 265 266 267BHttpForm::~BHttpForm() 268{ 269 Clear(); 270} 271 272 273// #pragma mark - Form string parsing 274 275 276void 277BHttpForm::ParseString(const BString& formString) 278{ 279 int32 index = 0; 280 281 while (index < formString.Length()) 282 _ExtractNameValuePair(formString, &index); 283} 284 285 286BString 287BHttpForm::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 373status_t 374BHttpForm::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 385status_t 386BHttpForm::AddInt(const BString& fieldName, int32 value) 387{ 388 BString strValue; 389 strValue << value; 390 391 return AddString(fieldName, strValue); 392} 393 394 395status_t 396BHttpForm::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 410status_t 411BHttpForm::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 423status_t 424BHttpForm::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 443void 444BHttpForm::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 458void 459BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename) 460{ 461 MarkAsFile(fieldName, filename, ""); 462} 463 464 465void 466BHttpForm::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 480void 481BHttpForm::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 493bool 494BHttpForm::HasField(const BString& name) const 495{ 496 return (fFields.find(name) != fFields.end()); 497} 498 499 500// #pragma mark - Form retrieve 501 502 503BString 504BHttpForm::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 515form_type 516BHttpForm::GetFormType() const 517{ 518 return fType; 519} 520 521 522const BString& 523BHttpForm::GetMultipartBoundary() const 524{ 525 return fMultipartBoundary; 526} 527 528 529BString 530BHttpForm::GetMultipartFooter() const 531{ 532 BString result = "--"; 533 result << fMultipartBoundary << "--\r\n"; 534 return result; 535} 536 537 538ssize_t 539BHttpForm::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 584BHttpForm::Iterator 585BHttpForm::GetIterator() 586{ 587 return BHttpForm::Iterator(this); 588} 589 590 591// #pragma mark - Form clear 592 593 594void 595BHttpForm::Clear() 596{ 597 fFields.clear(); 598} 599 600 601// #pragma mark - Overloaded operators 602 603 604BHttpFormData& 605BHttpForm::operator[](const BString& name) 606{ 607 if (!HasField(name)) 608 AddString(name, ""); 609 610 return fFields[name]; 611} 612 613 614void 615BHttpForm::_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 649void 650BHttpForm::_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 666BString 667BHttpForm::_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 717BHttpForm::Iterator::Iterator(BHttpForm* form) 718 : 719 fElement(NULL) 720{ 721 fForm = form; 722 fStdIterator = form->fFields.begin(); 723 _FindNext(); 724} 725 726 727BHttpForm::Iterator::Iterator(const Iterator& other) 728{ 729 *this = other; 730} 731 732 733bool 734BHttpForm::Iterator::HasNext() const 735{ 736 return fStdIterator != fForm->fFields.end(); 737} 738 739 740BHttpFormData* 741BHttpForm::Iterator::Next() 742{ 743 BHttpFormData* element = fElement; 744 _FindNext(); 745 return element; 746} 747 748 749void 750BHttpForm::Iterator::Remove() 751{ 752 fForm->fFields.erase(fStdIterator); 753 fElement = NULL; 754} 755 756 757BString 758BHttpForm::Iterator::MultipartHeader() 759{ 760 return fForm->_GetMultipartHeader(fPrevElement); 761} 762 763 764BHttpForm::Iterator& 765BHttpForm::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 776void 777BHttpForm::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