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