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
BHttpFormData()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
BHttpFormData(const BString & name,const BString & value)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
BHttpFormData(const BString & name,const BPath & file)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
BHttpFormData(const BString & name,const void * buffer,ssize_t size)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
BHttpFormData(const BHttpFormData & other)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
~BHttpFormData()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
InitCheck() const103 BHttpFormData::InitCheck() const
104 {
105 if (fDataType == B_HTTPFORM_BUFFER)
106 return fBufferValue != NULL;
107
108 return true;
109 }
110
111
112 const BString&
Name() const113 BHttpFormData::Name() const
114 {
115 return fName;
116 }
117
118
119 const BString&
String() const120 BHttpFormData::String() const
121 {
122 return fStringValue;
123 }
124
125
126 const BPath&
File() const127 BHttpFormData::File() const
128 {
129 return fPathValue;
130 }
131
132
133 const void*
Buffer() const134 BHttpFormData::Buffer() const
135 {
136 return fBufferValue;
137 }
138
139
140 ssize_t
BufferSize() const141 BHttpFormData::BufferSize() const
142 {
143 return fBufferSize;
144 }
145
146
147 bool
IsFile() const148 BHttpFormData::IsFile() const
149 {
150 return fFileMark;
151 }
152
153
154 const BString&
Filename() const155 BHttpFormData::Filename() const
156 {
157 return fFilename;
158 }
159
160
161 const BString&
MimeType() const162 BHttpFormData::MimeType() const
163 {
164 return fMimeType;
165 }
166
167
168 form_content_type
Type() const169 BHttpFormData::Type() const
170 {
171 return fDataType;
172 }
173
174
175 // #pragma mark - Change behavior
176
177
178 status_t
MarkAsFile(const BString & filename,const BString & mimeType)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
UnmarkAsFile()193 BHttpFormData::UnmarkAsFile()
194 {
195 fFilename.Truncate(0, true);
196 fMimeType.Truncate(0, true);
197 fFileMark = false;
198 }
199
200
201 status_t
CopyBuffer()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&
operator =(const BHttpFormData & other)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
BHttpForm()243 BHttpForm::BHttpForm()
244 :
245 fType(B_HTTP_FORM_URL_ENCODED)
246 {
247 }
248
249
BHttpForm(const BHttpForm & other)250 BHttpForm::BHttpForm(const BHttpForm& other)
251 :
252 fFields(other.fFields),
253 fType(other.fType),
254 fMultipartBoundary(other.fMultipartBoundary)
255 {
256 }
257
258
BHttpForm(const BString & formString)259 BHttpForm::BHttpForm(const BString& formString)
260 :
261 fType(B_HTTP_FORM_URL_ENCODED)
262 {
263 ParseString(formString);
264 }
265
266
~BHttpForm()267 BHttpForm::~BHttpForm()
268 {
269 Clear();
270 }
271
272
273 // #pragma mark - Form string parsing
274
275
276 void
ParseString(const BString & formString)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
RawData() const287 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
AddString(const BString & fieldName,const BString & value)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
AddInt(const BString & fieldName,int32 value)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
AddFile(const BString & fieldName,const BPath & file)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
AddBuffer(const BString & fieldName,const void * buffer,ssize_t size)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
AddBufferCopy(const BString & fieldName,const void * buffer,ssize_t size)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
MarkAsFile(const BString & fieldName,const BString & filename,const BString & mimeType)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
MarkAsFile(const BString & fieldName,const BString & filename)459 BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename)
460 {
461 MarkAsFile(fieldName, filename, "");
462 }
463
464
465 void
UnmarkAsFile(const BString & fieldName)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
SetFormType(form_type type)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
HasField(const BString & name) const494 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
GetMultipartHeader(const BString & fieldName) const504 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
GetFormType() const516 BHttpForm::GetFormType() const
517 {
518 return fType;
519 }
520
521
522 const BString&
GetMultipartBoundary() const523 BHttpForm::GetMultipartBoundary() const
524 {
525 return fMultipartBoundary;
526 }
527
528
529 BString
GetMultipartFooter() const530 BHttpForm::GetMultipartFooter() const
531 {
532 BString result = "--";
533 result << fMultipartBoundary << "--\r\n";
534 return result;
535 }
536
537
538 ssize_t
ContentLength() const539 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
GetIterator()585 BHttpForm::GetIterator()
586 {
587 return BHttpForm::Iterator(this);
588 }
589
590
591 // #pragma mark - Form clear
592
593
594 void
Clear()595 BHttpForm::Clear()
596 {
597 fFields.clear();
598 }
599
600
601 // #pragma mark - Overloaded operators
602
603
604 BHttpFormData&
operator [](const BString & name)605 BHttpForm::operator[](const BString& name)
606 {
607 if (!HasField(name))
608 AddString(name, "");
609
610 return fFields[name];
611 }
612
613
614 void
_ExtractNameValuePair(const BString & formString,int32 * index)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
_GenerateMultipartBoundary()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
_GetMultipartHeader(const BHttpFormData * element) const667 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
Iterator(BHttpForm * form)717 BHttpForm::Iterator::Iterator(BHttpForm* form)
718 :
719 fElement(NULL)
720 {
721 fForm = form;
722 fStdIterator = form->fFields.begin();
723 _FindNext();
724 }
725
726
Iterator(const Iterator & other)727 BHttpForm::Iterator::Iterator(const Iterator& other)
728 {
729 *this = other;
730 }
731
732
733 bool
HasNext() const734 BHttpForm::Iterator::HasNext() const
735 {
736 return fStdIterator != fForm->fFields.end();
737 }
738
739
740 BHttpFormData*
Next()741 BHttpForm::Iterator::Next()
742 {
743 BHttpFormData* element = fElement;
744 _FindNext();
745 return element;
746 }
747
748
749 void
Remove()750 BHttpForm::Iterator::Remove()
751 {
752 fForm->fFields.erase(fStdIterator);
753 fElement = NULL;
754 }
755
756
757 BString
MultipartHeader()758 BHttpForm::Iterator::MultipartHeader()
759 {
760 return fForm->_GetMultipartHeader(fPrevElement);
761 }
762
763
764 BHttpForm::Iterator&
operator =(const Iterator & other)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
_FindNext()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