xref: /haiku/src/kits/network/libnetservices/HttpForm.cpp (revision 1a76488fc88584bf66b9751d7fb9b6527ac20d87)
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