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