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