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