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 <deque>
12#include <new>
13
14#include <arpa/inet.h>
15#include <Debug.h>
16#include <File.h>
17#include <UrlProtocolHttp.h>
18
19
20using BPrivate::BUrlProtocolOption;
21
22
23static const int32 kHttpProtocolReceiveBufferSize = 1024;
24static const char* kHttpProtocolThreadStrStatus[
25		B_PROT_HTTP_THREAD_STATUS__END - B_PROT_THREAD_STATUS__END]
26	=  {
27		"The remote server did not found the requested resource"
28	};
29
30
31BUrlProtocolHttp::BUrlProtocolHttp(BUrl& url, BUrlProtocolListener* listener,
32		BUrlContext* context, BUrlResult* result)
33	:
34	BUrlProtocol(url, listener, context, result, "BUrlProtocol.HTTP", "HTTP"),
35	fRequestMethod(B_HTTP_GET),
36	fHttpVersion(B_HTTP_11)
37{
38	_ResetOptions();
39}
40
41
42status_t
43BUrlProtocolHttp::SetOption(uint32 name, void* value)
44{
45	BUrlProtocolOption option(value);
46
47	switch (name) {
48		case B_HTTPOPT_METHOD:
49			fRequestMethod = option.Int8();
50			break;
51
52		case B_HTTPOPT_FOLLOWLOCATION:
53			fOptFollowLocation = option.Bool();
54			break;
55
56		case B_HTTPOPT_MAXREDIRS:
57			fOptMaxRedirs = option.Int8();
58			break;
59
60		case B_HTTPOPT_REFERER:
61			fOptReferer = option.String();
62			break;
63
64		case B_HTTPOPT_USERAGENT:
65			fOptUserAgent = option.String();
66			break;
67
68		case B_HTTPOPT_HEADERS:
69			fOptHeaders = reinterpret_cast<BHttpHeaders*>(option.Pointer());
70			break;
71
72		case B_HTTPOPT_DISCARD_DATA:
73			fOptDiscardData = option.Bool();
74			break;
75
76		case B_HTTPOPT_DISABLE_LISTENER:
77			fOptDisableListener = option.Bool();
78			break;
79
80		case B_HTTPOPT_AUTOREFERER:
81			fOptAutoReferer = option.Bool();
82			break;
83
84		case B_HTTPOPT_POSTFIELDS:
85			fOptPostFields = reinterpret_cast<BHttpForm*>(option.Pointer());
86
87			if (fOptPostFields != NULL)
88				fRequestMethod = B_HTTP_POST;
89			break;
90
91		case B_HTTPOPT_INPUTDATA:
92			fOptInputData = reinterpret_cast<BDataIO*>(option.Pointer());
93			break;
94
95		case B_HTTPOPT_AUTHUSERNAME:
96			fOptUsername = option.String();
97			break;
98
99		case B_HTTPOPT_AUTHPASSWORD:
100			fOptPassword = option.String();
101			break;
102
103		default:
104			return B_ERROR;
105	}
106
107	return B_OK;
108}
109
110
111/*static*/ bool
112BUrlProtocolHttp::IsInformationalStatusCode(int16 code)
113{
114	return (code >= B_HTTP_STATUS__INFORMATIONAL_BASE)
115		&& (code <  B_HTTP_STATUS__INFORMATIONAL_END);
116}
117
118
119/*static*/ bool
120BUrlProtocolHttp::IsSuccessStatusCode(int16 code)
121{
122	return (code >= B_HTTP_STATUS__SUCCESS_BASE)
123		&& (code <  B_HTTP_STATUS__SUCCESS_END);
124}
125
126
127/*static*/ bool
128BUrlProtocolHttp::IsRedirectionStatusCode(int16 code)
129{
130	return (code >= B_HTTP_STATUS__REDIRECTION_BASE)
131		&& (code <  B_HTTP_STATUS__REDIRECTION_END);
132}
133
134
135/*static*/ bool
136BUrlProtocolHttp::IsClientErrorStatusCode(int16 code)
137{
138	return (code >= B_HTTP_STATUS__CLIENT_ERROR_BASE)
139		&& (code <  B_HTTP_STATUS__CLIENT_ERROR_END);
140}
141
142
143/*static*/ bool
144BUrlProtocolHttp::IsServerErrorStatusCode(int16 code)
145{
146	return (code >= B_HTTP_STATUS__SERVER_ERROR_BASE)
147		&& (code <  B_HTTP_STATUS__SERVER_ERROR_END);
148}
149
150
151/*static*/ int16
152BUrlProtocolHttp::StatusCodeClass(int16 code)
153{
154	if (BUrlProtocolHttp::IsInformationalStatusCode(code))
155		return B_HTTP_STATUS_CLASS_INFORMATIONAL;
156	else if (BUrlProtocolHttp::IsSuccessStatusCode(code))
157		return B_HTTP_STATUS_CLASS_SUCCESS;
158	else if (BUrlProtocolHttp::IsRedirectionStatusCode(code))
159		return B_HTTP_STATUS_CLASS_REDIRECTION;
160	else if (BUrlProtocolHttp::IsClientErrorStatusCode(code))
161		return B_HTTP_STATUS_CLASS_CLIENT_ERROR;
162	else if (BUrlProtocolHttp::IsServerErrorStatusCode(code))
163		return B_HTTP_STATUS_CLASS_SERVER_ERROR;
164
165	return B_HTTP_STATUS_CLASS_INVALID;
166}
167
168
169const char*
170BUrlProtocolHttp::StatusString(status_t threadStatus) const
171{
172	if (threadStatus < B_PROT_THREAD_STATUS__END)
173		return BUrlProtocol::StatusString(threadStatus);
174	else if (threadStatus >= B_PROT_HTTP_THREAD_STATUS__END)
175		return BUrlProtocol::StatusString(-1);
176	else
177		return kHttpProtocolThreadStrStatus[threadStatus
178			- B_PROT_THREAD_STATUS__END];
179}
180
181
182void
183BUrlProtocolHttp::_ResetOptions()
184{
185	fOptFollowLocation = true;
186	fOptMaxRedirs = 8;
187	fOptReferer	= "";
188	fOptUserAgent = "Services Kit (Haiku)";
189	fOptUsername = "";
190	fOptPassword = "";
191	fOptAuthMethods = B_HTTP_AUTHENTICATION_BASIC | B_HTTP_AUTHENTICATION_DIGEST
192		| B_HTTP_AUTHENTICATION_IE_DIGEST;
193	fOptHeaders	= NULL;
194	fOptPostFields = NULL;
195	fOptSetCookies = true;
196	fOptDiscardData = false;
197	fOptDisableListener = false;
198	fOptAutoReferer = true;
199}
200
201#include <stdio.h>
202status_t
203BUrlProtocolHttp::_ProtocolLoop()
204{
205	printf("UHP[%p]::{Loop} %s\n", this, fUrl.UrlString().String());
206	// Socket initialization
207	fSocket = BNetEndpoint(SOCK_STREAM);
208	if (fSocket.InitCheck() != B_OK)
209		return B_PROT_SOCKET_ERROR;
210
211	// Initialize the request redirection loop
212	int8 maxRedirs = fOptMaxRedirs;
213	bool newRequest;
214
215	do {
216		newRequest = false;
217
218		// Result reset
219		fOutputBuffer.Truncate(0, true);
220		fOutputHeaders.Clear();
221		fHeaders.Clear();
222		_ResultHeaders().Clear();
223		_ResultRawData().Seek(SEEK_SET, 0);
224		_ResultRawData().SetSize(0);
225
226		if (!_ResolveHostName()) {
227			_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
228				"Unable to resolve hostname, aborting.");
229			return B_PROT_CANT_RESOLVE_HOSTNAME;
230		}
231
232		_CreateRequest();
233		_AddHeaders();
234		_AddOutputBufferLine("");
235
236		status_t requestStatus = _MakeRequest();
237		if (requestStatus != B_PROT_SUCCESS)
238			return requestStatus;
239
240		// Prepare the referer for the next request if needed
241		if (fOptAutoReferer)
242			fOptReferer = fUrl.UrlString();
243
244		switch (StatusCodeClass(fResult->StatusCode())) {
245			case B_HTTP_STATUS_CLASS_INFORMATIONAL:
246				// Header 100:continue should have been
247				// handled in the _MakeRequest read loop
248				break;
249
250			case B_HTTP_STATUS_CLASS_SUCCESS:
251				break;
252
253			case B_HTTP_STATUS_CLASS_REDIRECTION:
254				// Redirection has been explicitly disabled
255				if (!fOptFollowLocation)
256					break;
257
258				//  TODO: Some browsers seems to translate POST requests to
259				// GET when following a 302 redirection
260				if (fResult->StatusCode() == B_HTTP_STATUS_MOVED_PERMANENTLY) {
261					BString locationUrl = fHeaders["Location"];
262
263					// Absolute path
264					if (locationUrl[0] == '/')
265						fUrl.SetPath(locationUrl);
266					// URI
267					else
268						fUrl.SetUrlString(locationUrl);
269
270					if (--maxRedirs > 0) {
271						newRequest = true;
272
273						_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
274							"Following: %s\n",
275							fUrl.UrlString().String());
276					}
277				}
278				break;
279
280			case B_HTTP_STATUS_CLASS_CLIENT_ERROR:
281				switch (fResult->StatusCode()) {
282					case B_HTTP_STATUS_UNAUTHORIZED:
283						if (fAuthentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
284							newRequest = false;
285							break;
286						}
287
288						newRequest = false;
289						if (fOptUsername.Length() > 0
290							&& fAuthentication.Initialize(fHeaders["WWW-Authenticate"])
291								== B_OK) {
292							fAuthentication.SetUserName(fOptUsername);
293							fAuthentication.SetPassword(fOptPassword);
294							newRequest = true;
295						}
296						break;
297				}
298				break;
299
300			case B_HTTP_STATUS_CLASS_SERVER_ERROR:
301				break;
302
303			default:
304			case B_HTTP_STATUS_CLASS_INVALID:
305				break;
306		}
307	} while (newRequest);
308
309	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
310		"%ld headers and %ld bytes of data remaining",
311		fHeaders.CountHeaders(),
312		fInputBuffer.Size());
313
314	if (fResult->StatusCode() == 404)
315		return B_PROT_HTTP_NOT_FOUND;
316
317	return B_PROT_SUCCESS;
318}
319
320
321bool
322BUrlProtocolHttp::_ResolveHostName()
323{
324	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Resolving %s",
325		fUrl.UrlString().String());
326
327	if (fUrl.HasPort())
328		fRemoteAddr = BNetAddress(fUrl.Host(), fUrl.Port());
329	else
330		fRemoteAddr = BNetAddress(fUrl.Host(), 80);
331
332	if (fRemoteAddr.InitCheck() != B_OK)
333		return false;
334
335	char addr[15];
336	struct in_addr ip;
337	fRemoteAddr.GetAddr(ip);
338	inet_ntop(AF_INET, &ip, addr, 15);
339
340	//! ProtocolHook:HostnameResolved
341	if (fListener != NULL)
342		fListener->HostnameResolved(this, const_cast<const char*>(addr));
343
344	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Hostname resolved to: %s", addr);
345
346	return true;
347}
348
349
350status_t
351BUrlProtocolHttp::_MakeRequest()
352{
353	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s.",
354		fUrl.Authority().String());
355	status_t connectError = fSocket.Connect(fRemoteAddr);
356
357	if (connectError != B_OK) {
358		_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Connection error: %s.",
359			fSocket.ErrorStr());
360		return B_PROT_CONNECTION_FAILED;
361	}
362
363	//! ProtocolHook:ConnectionOpened
364	if (fListener != NULL)
365		fListener->ConnectionOpened(this);
366
367	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection opened.");
368
369	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Sending request (size=%d)",
370		fOutputBuffer.Length());
371	fSocket.Send(fOutputBuffer.String(), fOutputBuffer.Length());
372	fOutputBuffer.Truncate(0);
373	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
374
375	if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) {
376		if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) {
377			fOutputBuffer = fOptPostFields->RawData();
378			_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
379				fOutputBuffer.String());
380			fSocket.Send(fOutputBuffer.String(), fOutputBuffer.Length());
381		} else {
382			for (BHttpForm::Iterator it = fOptPostFields->GetIterator();
383				const BHttpFormData* currentField = it.Next();
384				) {
385				_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
386					it.MultipartHeader().String());
387				fSocket.Send(it.MultipartHeader().String(),
388					it.MultipartHeader().Length());
389
390				switch (currentField->Type()) {
391					case B_HTTPFORM_UNKNOWN:
392						ASSERT(0);
393						break;
394
395					case B_HTTPFORM_STRING:
396						fSocket.Send(currentField->String().String(),
397							currentField->String().Length());
398						break;
399
400					case B_HTTPFORM_FILE:
401						{
402							BFile upFile(currentField->File().Path(),
403								B_READ_ONLY);
404							char readBuffer[1024];
405							ssize_t readSize;
406
407							readSize = upFile.Read(readBuffer, 1024);
408							while (readSize > 0) {
409								fSocket.Send(readBuffer, readSize);
410								readSize = upFile.Read(readBuffer, 1024);
411							}
412						}
413						break;
414
415					case B_HTTPFORM_BUFFER:
416						fSocket.Send(currentField->Buffer(),
417							currentField->BufferSize());
418						break;
419				}
420
421				fSocket.Send("\r\n", 2);
422			}
423
424			fSocket.Send(fOptPostFields->GetMultipartFooter().String(),
425				fOptPostFields->GetMultipartFooter().Length());
426		}
427	} else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)
428		&& fOptInputData != NULL) {
429		char outputTempBuffer[1024];
430		ssize_t read = 0;
431
432		while (read != -1) {
433			read = fOptInputData->Read(outputTempBuffer, 1024);
434
435			if (read > 0) {
436				char hexSize[16];
437				size_t hexLength = sprintf(hexSize, "%ld", read);
438
439				fSocket.Send(hexSize, hexLength);
440				fSocket.Send("\r\n", 2);
441				fSocket.Send(outputTempBuffer, read);
442				fSocket.Send("\r\n", 2);
443			}
444		}
445
446		fSocket.Send("0\r\n\r\n", 5);
447	}
448	fOutputBuffer.Truncate(0, true);
449
450	fSocket.SetNonBlocking(false);
451
452	fStatusReceived = false;
453	fHeadersReceived = false;
454
455	// Receive loop
456	bool receiveEnd = false;
457	bool parseEnd = false;
458	bool readByChunks = false;
459	bool readError = false;
460	int32 receiveBufferSize = 32;
461	ssize_t bytesRead = 0;
462	ssize_t bytesReceived = 0;
463	ssize_t bytesTotal = 0;
464	char* inputTempBuffer = NULL;
465	fQuit = false;
466
467	while (!fQuit && !(receiveEnd && parseEnd)) {
468		if (!receiveEnd) {
469			 bytesRead = fSocket.Receive(fInputBuffer, receiveBufferSize);
470
471			if (bytesRead < 0) {
472				readError = true;
473				fQuit = true;
474				continue;
475			} else if (bytesRead == 0)
476				receiveEnd = true;
477		}
478		else
479			bytesRead = 0;
480
481		if (!fStatusReceived) {
482			_ParseStatus();
483
484			//! ProtocolHook:ResponseStarted
485			if (fStatusReceived && fListener != NULL)
486				fListener->ResponseStarted(this);
487		} else if (!fHeadersReceived) {
488			_ParseHeaders();
489
490			if (fHeadersReceived) {
491				receiveBufferSize = kHttpProtocolReceiveBufferSize;
492				_ResultHeaders() = fHeaders;
493
494				//! ProtocolHook:HeadersReceived
495				if (fListener != NULL)
496					fListener->HeadersReceived(this);
497
498				// Parse received cookies
499				if ((fContext != NULL) && fHeaders.HasHeader("Set-Cookie")) {
500					for (int32 i = 0;  i < fHeaders.CountHeaders(); i++) {
501						if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
502						}
503					}
504				}
505
506				if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
507					readByChunks = true;
508
509				int32 index = fHeaders.HasHeader("Content-Length");
510				if (index != B_ERROR)
511					bytesTotal = atoi(fHeaders.HeaderAt(index).Value());
512				else
513					bytesTotal = 0;
514			}
515		} else {
516			// If Transfer-Encoding is chunked, we should read a complete
517			// chunk in buffer before handling it
518			if (readByChunks) {
519				_CopyChunkInBuffer(&inputTempBuffer, &bytesRead);
520
521				// A chunk of 0 bytes indicates the end of the chunked transfer
522				if (bytesRead == 0) {
523					receiveEnd = true;
524				}
525			}
526			else {
527				bytesRead = fInputBuffer.Size();
528
529				if (bytesRead > 0) {
530					inputTempBuffer = new char[bytesRead];
531					fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
532				}
533			}
534
535			if (bytesRead > 0) {
536				bytesReceived += bytesRead;
537				_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_IN, "%d bytes",
538					bytesRead);
539
540				if (fListener != NULL) {
541					fListener->DataReceived(this, inputTempBuffer, bytesRead);
542					fListener->DownloadProgress(this, bytesReceived,
543						bytesTotal);
544				}
545
546				ssize_t dataWrite = _ResultRawData().Write(inputTempBuffer,
547					bytesRead);
548
549				if (dataWrite != bytesRead) {
550					_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
551						"Unable to write %dbytes of data (%d).", bytesRead,
552						dataWrite);
553					return B_PROT_NO_MEMORY;
554				}
555
556				if (bytesTotal > 0 && bytesReceived >= bytesTotal)
557					receiveEnd = true;
558
559				delete[] inputTempBuffer;
560			}
561		}
562
563		parseEnd = (fInputBuffer.Size() == 0);
564	}
565
566	fSocket.Close();
567
568	if (readError)
569		return B_PROT_READ_FAILED;
570
571	return fQuit?B_PROT_ABORTED:B_PROT_SUCCESS;
572}
573
574
575status_t
576BUrlProtocolHttp::_GetLine(BString& destString)
577{
578	// Find a complete line in inputBuffer
579	uint32 characterIndex = 0;
580
581	while ((characterIndex < fInputBuffer.Size())
582		&& ((fInputBuffer.Data())[characterIndex] != '\n'))
583		characterIndex++;
584
585	if (characterIndex == fInputBuffer.Size())
586		return B_ERROR;
587
588	char* temporaryBuffer = new(std::nothrow) char[characterIndex + 1];
589	fInputBuffer.RemoveData(temporaryBuffer, characterIndex + 1);
590
591	// Strip end-of-line character(s)
592	if (temporaryBuffer[characterIndex-1] == '\r')
593		destString.SetTo(temporaryBuffer, characterIndex - 1);
594	else
595		destString.SetTo(temporaryBuffer, characterIndex);
596
597	delete[] temporaryBuffer;
598	return B_OK;
599}
600
601
602void
603BUrlProtocolHttp::_ParseStatus()
604{
605	// Status line should be formatted like: HTTP/M.m SSS ...
606	// With:   M = Major version of the protocol
607	//         m = Minor version of the protocol
608	//       SSS = three-digit status code of the response
609	//       ... = additional text info
610	BString statusLine;
611	if (_GetLine(statusLine) == B_ERROR)
612		return;
613
614	if (statusLine.CountChars() < 12)
615		return;
616
617	fStatusReceived = true;
618
619	BString statusCodeStr;
620	BString statusText;
621	statusLine.CopyInto(statusCodeStr, 9, 3);
622	_SetResultStatusCode(atoi(statusCodeStr.String()));
623
624	statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13);
625
626	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)",
627		atoi(statusCodeStr.String()), _ResultStatusText().String());
628}
629
630
631void
632BUrlProtocolHttp::_ParseHeaders()
633{
634	BString currentHeader;
635	if (_GetLine(currentHeader) == B_ERROR)
636		return;
637
638	// Empty line
639	if (currentHeader.Length() == 0) {
640		fHeadersReceived = true;
641		return;
642	}
643
644	_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s", currentHeader.String());
645	fHeaders.AddHeader(currentHeader.String());
646}
647
648
649void
650BUrlProtocolHttp::_CopyChunkInBuffer(char** buffer, ssize_t* bytesReceived)
651{
652	static ssize_t chunkSize = -1;
653	BString chunkHeader;
654
655	if (chunkSize >= 0) {
656		if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2)  {
657			// 2 more bytes to handle the closing CR+LF
658			*bytesReceived = chunkSize;
659			*buffer = new char[chunkSize+2];
660			fInputBuffer.RemoveData(*buffer, chunkSize+2);
661			chunkSize = -1;
662		} else {
663			*bytesReceived = -1;
664			*buffer = NULL;
665		}
666	} else {
667		if (_GetLine(chunkHeader) == B_ERROR) {
668			chunkSize = -1;
669			*buffer = NULL;
670			*bytesReceived = -1;
671			return;
672		}
673
674		// Format of a chunk header:
675		// <chunk size in hex>[; optional data]
676		int32 semiColonIndex = chunkHeader.FindFirst(";", 0);
677
678		// Cut-off optional data if present
679		if (semiColonIndex != -1)
680			chunkHeader.Remove(semiColonIndex,
681				chunkHeader.Length() - semiColonIndex);
682
683		chunkSize = strtol(chunkHeader.String(), NULL, 16);
684		PRINT(("BHP[%p] Chunk %s=%d\n", this, chunkHeader.String(), chunkSize));
685		if (chunkSize == 0) {
686			fContentReceived = true;
687		}
688
689		*bytesReceived = -1;
690		*buffer = NULL;
691	}
692}
693
694
695void
696BUrlProtocolHttp::_CreateRequest()
697{
698	BString request;
699
700	switch (fRequestMethod) {
701		case B_HTTP_POST:
702			request << "POST";
703			break;
704
705		case B_HTTP_PUT:
706			request << "PUT";
707			break;
708
709		default:
710		case B_HTTP_GET:
711			request << "GET";
712			break;
713	}
714
715	if (Url().HasPath())
716		request << ' ' << Url().Path();
717	else
718		request << " /";
719
720	if (Url().HasRequest())
721		request << '?' << Url().Request();
722
723	if (Url().HasFragment())
724		request << '#' << Url().Fragment();
725
726	request << ' ';
727
728	switch (fHttpVersion) {
729		case B_HTTP_11:
730			request << "HTTP/1.1";
731			break;
732
733		default:
734		case B_HTTP_10:
735			request << "HTTP/1.0";
736			break;
737	}
738
739	_AddOutputBufferLine(request.String());
740}
741
742
743void
744BUrlProtocolHttp::_AddHeaders()
745{
746	// HTTP 1.1 additional headers
747	if (fHttpVersion == B_HTTP_11) {
748		fOutputHeaders.AddHeader("Host", Url().Host());
749
750		fOutputHeaders.AddHeader("Accept", "*/*");
751		fOutputHeaders.AddHeader("Accept-Encoding", "chunked");
752			//  Allow the remote server to send dynamic content by chunks
753			// rather than waiting for the full content to be generated and
754			// sending us data.
755
756		fOutputHeaders.AddHeader("Connection", "close");
757			//  Let the remote server close the connection after response since
758			// we don't handle multiple request on a single connection
759	}
760
761	// Classic HTTP headers
762	if (fOptUserAgent.CountChars() > 0)
763		fOutputHeaders.AddHeader("User-Agent", fOptUserAgent.String());
764
765	if (fOptReferer.CountChars() > 0)
766		fOutputHeaders.AddHeader("Referer", fOptReferer.String());
767
768	// Authentication
769	if (fAuthentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
770		BString request;
771		switch (fRequestMethod) {
772			case B_HTTP_POST:
773				request = "POST";
774				break;
775
776			case B_HTTP_PUT:
777				request = "PUT";
778				break;
779
780			default:
781			case B_HTTP_GET:
782				request = "GET";
783				break;
784		}
785
786		fOutputHeaders.AddHeader("Authorization",
787			fAuthentication.Authorization(fUrl, request));
788	}
789
790	// Required headers for POST data
791	if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
792		BString contentType;
793
794		switch (fOptPostFields->GetFormType()) {
795			case B_HTTP_FORM_MULTIPART:
796				contentType << "multipart/form-data; boundary="
797					<< fOptPostFields->GetMultipartBoundary() << "";
798				break;
799
800			case B_HTTP_FORM_URL_ENCODED:
801				contentType << "application/x-www-form-urlencoded";
802				break;
803		}
804
805		fOutputHeaders.AddHeader("Content-Type", contentType);
806		fOutputHeaders.AddHeader("Content-Length",
807			fOptPostFields->ContentLength());
808	} else if (fOptInputData != NULL
809		&& (fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT))
810		fOutputHeaders.AddHeader("Transfer-Encoding", "chunked");
811
812	// Request headers
813	for (int32 headerIndex = 0;
814		headerIndex < fRequestHeaders.CountHeaders();
815		headerIndex++) {
816		BHttpHeader& optHeader = fRequestHeaders[headerIndex];
817		int32 replaceIndex = fOutputHeaders.HasHeader(optHeader.Name());
818
819		//  Add or replace the current option header to the
820		// output header list
821		if (replaceIndex == -1)
822			fOutputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
823		else
824			fOutputHeaders[replaceIndex].SetValue(optHeader.Value());
825	}
826
827	// Optional headers specified by the user
828	if (fOptHeaders != NULL) {
829		for (int32 headerIndex = 0;
830			headerIndex < fOptHeaders->CountHeaders();
831			headerIndex++) {
832			BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
833			int32 replaceIndex = fOutputHeaders.HasHeader(optHeader.Name());
834
835			//  Add or replace the current option header to the
836			// output header list
837			if (replaceIndex == -1)
838				fOutputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
839			else
840				fOutputHeaders[replaceIndex].SetValue(optHeader.Value());
841		}
842	}
843
844	// Context cookies
845	if (fOptSetCookies && (fContext != NULL)) {
846		BNetworkCookie* cookie;
847
848		for (BNetworkCookieJar::UrlIterator
849			it(fContext->GetCookieJar().GetUrlIterator(fUrl));
850			(cookie = it.Next()) != NULL;
851			)
852				fOutputHeaders.AddHeader("Cookie", cookie->RawCookie(false));
853	}
854
855	// Write output headers to output stream
856	for (int32 headerIndex = 0;
857		headerIndex < fOutputHeaders.CountHeaders();
858		headerIndex++)
859		_AddOutputBufferLine(fOutputHeaders.HeaderAt(headerIndex).Header());
860}
861
862
863void
864BUrlProtocolHttp::_AddOutputBufferLine(const char* line)
865{
866	_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", line);
867	fOutputBuffer << line << "\r\n";
868}
869