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;
25using namespace BPrivate::Network;
26
27
28// #pragma mark - BHttpFormData
29
30
31BHttpFormData::BHttpFormData()
32	:
33	fDataType(B_HTTPFORM_STRING),
34	fCopiedBuffer(false),
35	fFileMark(false),
36	fBufferValue(NULL),
37	fBufferSize(0)
38{
39}
40
41
42BHttpFormData::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
55BHttpFormData::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
68BHttpFormData::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
81BHttpFormData::BHttpFormData(const BHttpFormData& other)
82	:
83	fCopiedBuffer(false),
84	fFileMark(false),
85	fBufferValue(NULL),
86	fBufferSize(0)
87{
88	*this = other;
89}
90
91
92BHttpFormData::~BHttpFormData()
93{
94	if (fCopiedBuffer)
95		delete[] reinterpret_cast<const char*>(fBufferValue);
96}
97
98
99// #pragma mark - Retrieve data informations
100
101
102bool
103BHttpFormData::InitCheck() const
104{
105	if (fDataType == B_HTTPFORM_BUFFER)
106		return fBufferValue != NULL;
107
108	return true;
109}
110
111
112const BString&
113BHttpFormData::Name() const
114{
115	return fName;
116}
117
118
119const BString&
120BHttpFormData::String() const
121{
122	return fStringValue;
123}
124
125
126const BPath&
127BHttpFormData::File() const
128{
129	return fPathValue;
130}
131
132
133const void*
134BHttpFormData::Buffer() const
135{
136	return fBufferValue;
137}
138
139
140ssize_t
141BHttpFormData::BufferSize() const
142{
143	return fBufferSize;
144}
145
146
147bool
148BHttpFormData::IsFile() const
149{
150	return fFileMark;
151}
152
153
154const BString&
155BHttpFormData::Filename() const
156{
157	return fFilename;
158}
159
160
161const BString&
162BHttpFormData::MimeType() const
163{
164	return fMimeType;
165}
166
167
168form_content_type
169BHttpFormData::Type() const
170{
171	return fDataType;
172}
173
174
175// #pragma mark - Change behavior
176
177
178status_t
179BHttpFormData::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
192void
193BHttpFormData::UnmarkAsFile()
194{
195	fFilename.Truncate(0, true);
196	fMimeType.Truncate(0, true);
197	fFileMark = false;
198}
199
200
201status_t
202BHttpFormData::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
219BHttpFormData&
220BHttpFormData::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
243BHttpForm::BHttpForm()
244	:
245	fType(B_HTTP_FORM_URL_ENCODED)
246{
247}
248
249
250BHttpForm::BHttpForm(const BHttpForm& other)
251	:
252	fFields(other.fFields),
253	fType(other.fType),
254	fMultipartBoundary(other.fMultipartBoundary)
255{
256}
257
258
259BHttpForm::BHttpForm(const BString& formString)
260	:
261	fType(B_HTTP_FORM_URL_ENCODED)
262{
263	ParseString(formString);
264}
265
266
267BHttpForm::~BHttpForm()
268{
269	Clear();
270}
271
272
273// #pragma mark - Form string parsing
274
275
276void
277BHttpForm::ParseString(const BString& formString)
278{
279	int32 index = 0;
280
281	while (index < formString.Length())
282		_ExtractNameValuePair(formString, &index);
283}
284
285
286BString
287BHttpForm::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
373status_t
374BHttpForm::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
385status_t
386BHttpForm::AddInt(const BString& fieldName, int32 value)
387{
388	BString strValue;
389	strValue << value;
390
391	return AddString(fieldName, strValue);
392}
393
394
395status_t
396BHttpForm::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
410status_t
411BHttpForm::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
423status_t
424BHttpForm::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
443void
444BHttpForm::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
458void
459BHttpForm::MarkAsFile(const BString& fieldName, const BString& filename)
460{
461	MarkAsFile(fieldName, filename, "");
462}
463
464
465void
466BHttpForm::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
480void
481BHttpForm::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
493bool
494BHttpForm::HasField(const BString& name) const
495{
496	return (fFields.find(name) != fFields.end());
497}
498
499
500// #pragma mark - Form retrieve
501
502
503BString
504BHttpForm::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
515form_type
516BHttpForm::GetFormType() const
517{
518	return fType;
519}
520
521
522const BString&
523BHttpForm::GetMultipartBoundary() const
524{
525	return fMultipartBoundary;
526}
527
528
529BString
530BHttpForm::GetMultipartFooter() const
531{
532	BString result = "--";
533	result << fMultipartBoundary << "--\r\n";
534	return result;
535}
536
537
538ssize_t
539BHttpForm::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
584BHttpForm::Iterator
585BHttpForm::GetIterator()
586{
587	return BHttpForm::Iterator(this);
588}
589
590
591// #pragma mark - Form clear
592
593
594void
595BHttpForm::Clear()
596{
597	fFields.clear();
598}
599
600
601// #pragma mark - Overloaded operators
602
603
604BHttpFormData&
605BHttpForm::operator[](const BString& name)
606{
607	if (!HasField(name))
608		AddString(name, "");
609
610	return fFields[name];
611}
612
613
614void
615BHttpForm::_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
649void
650BHttpForm::_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
666BString
667BHttpForm::_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
717BHttpForm::Iterator::Iterator(BHttpForm* form)
718	:
719	fElement(NULL)
720{
721	fForm = form;
722	fStdIterator = form->fFields.begin();
723	_FindNext();
724}
725
726
727BHttpForm::Iterator::Iterator(const Iterator& other)
728{
729	*this = other;
730}
731
732
733bool
734BHttpForm::Iterator::HasNext() const
735{
736	return fStdIterator != fForm->fFields.end();
737}
738
739
740BHttpFormData*
741BHttpForm::Iterator::Next()
742{
743	BHttpFormData* element = fElement;
744	_FindNext();
745	return element;
746}
747
748
749void
750BHttpForm::Iterator::Remove()
751{
752	fForm->fFields.erase(fStdIterator);
753	fElement = NULL;
754}
755
756
757BString
758BHttpForm::Iterator::MultipartHeader()
759{
760	return fForm->_GetMultipartHeader(fPrevElement);
761}
762
763
764BHttpForm::Iterator&
765BHttpForm::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
776void
777BHttpForm::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