xref: /haiku/src/kits/network/libnetservices/HttpRequest.cpp (revision 4a32f48e70297d9a634646f01e08c2f451ecd6bd)
1 /*
2  * Copyright 2010-2021 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  *		Niels Sascha Reedijk, niels.reedijk@gmail.com
8  *		Adrien Destugues, pulkomandy@pulkomandy.tk
9  *		Stephan Aßmus, superstippi@gmx.de
10  */
11 
12 
13 #include <HttpRequest.h>
14 
15 #include <arpa/inet.h>
16 #include <stdio.h>
17 
18 #include <cstdlib>
19 #include <deque>
20 #include <new>
21 
22 #include <AutoDeleter.h>
23 #include <Certificate.h>
24 #include <Debug.h>
25 #include <DynamicBuffer.h>
26 #include <File.h>
27 #include <ProxySecureSocket.h>
28 #include <Socket.h>
29 #include <SecureSocket.h>
30 #include <StackOrHeapArray.h>
31 #include <ZlibCompressionAlgorithm.h>
32 
33 
34 static const int32 kHttpBufferSize = 4096;
35 
36 
37 #ifndef LIBNETAPI_DEPRECATED
38 using namespace BPrivate::Network;
39 #endif
40 
41 
42 namespace BPrivate {
43 
44 	class CheckedSecureSocket: public BSecureSocket
45 	{
46 		public:
47 			CheckedSecureSocket(BHttpRequest* request);
48 
49 			bool			CertificateVerificationFailed(BCertificate& certificate,
50 					const char* message);
51 
52 		private:
53 			BHttpRequest*	fRequest;
54 	};
55 
56 
57 	CheckedSecureSocket::CheckedSecureSocket(BHttpRequest* request)
58 		:
59 		BSecureSocket(),
60 		fRequest(request)
61 	{
62 	}
63 
64 
65 	bool
66 	CheckedSecureSocket::CertificateVerificationFailed(BCertificate& certificate,
67 		const char* message)
68 	{
69 		return fRequest->_CertificateVerificationFailed(certificate, message);
70 	}
71 
72 
73 	class CheckedProxySecureSocket: public BProxySecureSocket
74 	{
75 		public:
76 			CheckedProxySecureSocket(const BNetworkAddress& proxy, BHttpRequest* request);
77 
78 			bool			CertificateVerificationFailed(BCertificate& certificate,
79 					const char* message);
80 
81 		private:
82 			BHttpRequest*	fRequest;
83 	};
84 
85 
86 	CheckedProxySecureSocket::CheckedProxySecureSocket(const BNetworkAddress& proxy,
87 		BHttpRequest* request)
88 		:
89 		BProxySecureSocket(proxy),
90 		fRequest(request)
91 	{
92 	}
93 
94 
95 	bool
96 	CheckedProxySecureSocket::CertificateVerificationFailed(BCertificate& certificate,
97 		const char* message)
98 	{
99 		return fRequest->_CertificateVerificationFailed(certificate, message);
100 	}
101 };
102 
103 
104 #ifdef LIBNETAPI_DEPRECATED
105 BHttpRequest::BHttpRequest(const BUrl& url, bool ssl, const char* protocolName,
106 	BUrlProtocolListener* listener, BUrlContext* context)
107 	:
108 	BNetworkRequest(url, listener, context, "BUrlProtocol.HTTP", protocolName),
109 	fSSL(ssl),
110 	fRequestMethod(B_HTTP_GET),
111 	fHttpVersion(B_HTTP_11),
112 	fResult(url),
113 	fRequestStatus(kRequestInitialState),
114 	fOptHeaders(NULL),
115 	fOptPostFields(NULL),
116 	fOptInputData(NULL),
117 	fOptInputDataSize(-1),
118 	fOptRangeStart(-1),
119 	fOptRangeEnd(-1),
120 	fOptFollowLocation(true)
121 {
122 	_ResetOptions();
123 	fSocket = NULL;
124 }
125 
126 
127 BHttpRequest::BHttpRequest(const BHttpRequest& other)
128 	:
129 	BNetworkRequest(other.Url(), other.fListener, other.fContext,
130 		"BUrlProtocol.HTTP", other.fSSL ? "HTTPS" : "HTTP"),
131 	fSSL(other.fSSL),
132 	fRequestMethod(other.fRequestMethod),
133 	fHttpVersion(other.fHttpVersion),
134 	fResult(other.fUrl),
135 	fRequestStatus(kRequestInitialState),
136 	fOptHeaders(NULL),
137 	fOptPostFields(NULL),
138 	fOptInputData(NULL),
139 	fOptInputDataSize(-1),
140 	fOptRangeStart(other.fOptRangeStart),
141 	fOptRangeEnd(other.fOptRangeEnd),
142 	fOptFollowLocation(other.fOptFollowLocation)
143 {
144 	_ResetOptions();
145 		// FIXME some options may be copied from other instead.
146 	fSocket = NULL;
147 }
148 
149 #else
150 
151 BHttpRequest::BHttpRequest(const BUrl& url, BDataIO* output, bool ssl,
152 	const char* protocolName, BUrlProtocolListener* listener,
153 	BUrlContext* context)
154 	:
155 	BNetworkRequest(url, output, listener, context, "BUrlProtocol.HTTP",
156 		protocolName),
157 	fSSL(ssl),
158 	fRequestMethod(B_HTTP_GET),
159 	fHttpVersion(B_HTTP_11),
160 	fResult(url),
161 	fRequestStatus(kRequestInitialState),
162 	fOptHeaders(NULL),
163 	fOptPostFields(NULL),
164 	fOptInputData(NULL),
165 	fOptInputDataSize(-1),
166 	fOptRangeStart(-1),
167 	fOptRangeEnd(-1),
168 	fOptFollowLocation(true)
169 {
170 	_ResetOptions();
171 	fSocket = NULL;
172 }
173 
174 
175 BHttpRequest::BHttpRequest(const BHttpRequest& other)
176 	:
177 	BNetworkRequest(other.Url(), other.Output(), other.fListener,
178 		other.fContext, "BUrlProtocol.HTTP", other.fSSL ? "HTTPS" : "HTTP"),
179 	fSSL(other.fSSL),
180 	fRequestMethod(other.fRequestMethod),
181 	fHttpVersion(other.fHttpVersion),
182 	fResult(other.fUrl),
183 	fRequestStatus(kRequestInitialState),
184 	fOptHeaders(NULL),
185 	fOptPostFields(NULL),
186 	fOptInputData(NULL),
187 	fOptInputDataSize(-1),
188 	fOptRangeStart(other.fOptRangeStart),
189 	fOptRangeEnd(other.fOptRangeEnd),
190 	fOptFollowLocation(other.fOptFollowLocation)
191 {
192 	_ResetOptions();
193 		// FIXME some options may be copied from other instead.
194 	fSocket = NULL;
195 }
196 #endif // LIBNETAPI_DEPRECATED
197 
198 
199 BHttpRequest::~BHttpRequest()
200 {
201 	Stop();
202 
203 	delete fSocket;
204 
205 	delete fOptInputData;
206 	delete fOptHeaders;
207 	delete fOptPostFields;
208 }
209 
210 
211 void
212 BHttpRequest::SetMethod(const char* const method)
213 {
214 	fRequestMethod = method;
215 }
216 
217 
218 void
219 BHttpRequest::SetFollowLocation(bool follow)
220 {
221 	fOptFollowLocation = follow;
222 }
223 
224 
225 void
226 BHttpRequest::SetMaxRedirections(int8 redirections)
227 {
228 	fOptMaxRedirs = redirections;
229 }
230 
231 
232 void
233 BHttpRequest::SetReferrer(const BString& referrer)
234 {
235 	fOptReferer = referrer;
236 }
237 
238 
239 void
240 BHttpRequest::SetUserAgent(const BString& agent)
241 {
242 	fOptUserAgent = agent;
243 }
244 
245 
246 void
247 BHttpRequest::SetDiscardData(bool discard)
248 {
249 	fOptDiscardData = discard;
250 }
251 
252 
253 void
254 BHttpRequest::SetDisableListener(bool disable)
255 {
256 	fOptDisableListener = disable;
257 }
258 
259 
260 void
261 BHttpRequest::SetAutoReferrer(bool enable)
262 {
263 	fOptAutoReferer = enable;
264 }
265 
266 
267 #ifndef LIBNETAPI_DEPRECATED
268 void
269 BHttpRequest::SetStopOnError(bool stop)
270 {
271 	fOptStopOnError = stop;
272 }
273 #endif
274 
275 
276 void
277 BHttpRequest::SetUserName(const BString& name)
278 {
279 	fOptUsername = name;
280 }
281 
282 
283 void
284 BHttpRequest::SetPassword(const BString& password)
285 {
286 	fOptPassword = password;
287 }
288 
289 
290 void
291 BHttpRequest::SetRangeStart(off_t position)
292 {
293 	// This field is used within the transfer loop, so only
294 	// allow setting it before sending the request.
295 	if (fRequestStatus == kRequestInitialState)
296 		fOptRangeStart = position;
297 }
298 
299 
300 void
301 BHttpRequest::SetRangeEnd(off_t position)
302 {
303 	// This field could be used in the transfer loop, so only
304 	// allow setting it before sending the request.
305 	if (fRequestStatus == kRequestInitialState)
306 		fOptRangeEnd = position;
307 }
308 
309 
310 void
311 BHttpRequest::SetPostFields(const BHttpForm& fields)
312 {
313 	AdoptPostFields(new(std::nothrow) BHttpForm(fields));
314 }
315 
316 
317 void
318 BHttpRequest::SetHeaders(const BHttpHeaders& headers)
319 {
320 	AdoptHeaders(new(std::nothrow) BHttpHeaders(headers));
321 }
322 
323 
324 void
325 BHttpRequest::AdoptPostFields(BHttpForm* const fields)
326 {
327 	delete fOptPostFields;
328 	fOptPostFields = fields;
329 
330 	if (fOptPostFields != NULL)
331 		fRequestMethod = B_HTTP_POST;
332 }
333 
334 
335 void
336 BHttpRequest::AdoptInputData(BDataIO* const data, const ssize_t size)
337 {
338 	delete fOptInputData;
339 	fOptInputData = data;
340 	fOptInputDataSize = size;
341 }
342 
343 
344 void
345 BHttpRequest::AdoptHeaders(BHttpHeaders* const headers)
346 {
347 	delete fOptHeaders;
348 	fOptHeaders = headers;
349 }
350 
351 
352 /*static*/ bool
353 BHttpRequest::IsInformationalStatusCode(int16 code)
354 {
355 	return (code >= B_HTTP_STATUS__INFORMATIONAL_BASE)
356 		&& (code <  B_HTTP_STATUS__INFORMATIONAL_END);
357 }
358 
359 
360 /*static*/ bool
361 BHttpRequest::IsSuccessStatusCode(int16 code)
362 {
363 	return (code >= B_HTTP_STATUS__SUCCESS_BASE)
364 		&& (code <  B_HTTP_STATUS__SUCCESS_END);
365 }
366 
367 
368 /*static*/ bool
369 BHttpRequest::IsRedirectionStatusCode(int16 code)
370 {
371 	return (code >= B_HTTP_STATUS__REDIRECTION_BASE)
372 		&& (code <  B_HTTP_STATUS__REDIRECTION_END);
373 }
374 
375 
376 /*static*/ bool
377 BHttpRequest::IsClientErrorStatusCode(int16 code)
378 {
379 	return (code >= B_HTTP_STATUS__CLIENT_ERROR_BASE)
380 		&& (code <  B_HTTP_STATUS__CLIENT_ERROR_END);
381 }
382 
383 
384 /*static*/ bool
385 BHttpRequest::IsServerErrorStatusCode(int16 code)
386 {
387 	return (code >= B_HTTP_STATUS__SERVER_ERROR_BASE)
388 		&& (code <  B_HTTP_STATUS__SERVER_ERROR_END);
389 }
390 
391 
392 /*static*/ int16
393 BHttpRequest::StatusCodeClass(int16 code)
394 {
395 	if (BHttpRequest::IsInformationalStatusCode(code))
396 		return B_HTTP_STATUS_CLASS_INFORMATIONAL;
397 	else if (BHttpRequest::IsSuccessStatusCode(code))
398 		return B_HTTP_STATUS_CLASS_SUCCESS;
399 	else if (BHttpRequest::IsRedirectionStatusCode(code))
400 		return B_HTTP_STATUS_CLASS_REDIRECTION;
401 	else if (BHttpRequest::IsClientErrorStatusCode(code))
402 		return B_HTTP_STATUS_CLASS_CLIENT_ERROR;
403 	else if (BHttpRequest::IsServerErrorStatusCode(code))
404 		return B_HTTP_STATUS_CLASS_SERVER_ERROR;
405 
406 	return B_HTTP_STATUS_CLASS_INVALID;
407 }
408 
409 
410 const BUrlResult&
411 BHttpRequest::Result() const
412 {
413 	return fResult;
414 }
415 
416 
417 status_t
418 BHttpRequest::Stop()
419 {
420 	if (fSocket != NULL) {
421 		fSocket->Disconnect();
422 			// Unlock any pending connect, read or write operation.
423 	}
424 	return BNetworkRequest::Stop();
425 }
426 
427 
428 void
429 BHttpRequest::_ResetOptions()
430 {
431 	delete fOptPostFields;
432 	delete fOptHeaders;
433 
434 	fOptFollowLocation = true;
435 	fOptMaxRedirs = 8;
436 	fOptReferer = "";
437 	fOptUserAgent = "Services Kit (Haiku)";
438 	fOptUsername = "";
439 	fOptPassword = "";
440 	fOptAuthMethods = B_HTTP_AUTHENTICATION_BASIC | B_HTTP_AUTHENTICATION_DIGEST
441 		| B_HTTP_AUTHENTICATION_IE_DIGEST;
442 	fOptHeaders = NULL;
443 	fOptPostFields = NULL;
444 	fOptSetCookies = true;
445 	fOptDiscardData = false;
446 	fOptDisableListener = false;
447 	fOptAutoReferer = true;
448 }
449 
450 
451 status_t
452 BHttpRequest::_ProtocolLoop()
453 {
454 	// Initialize the request redirection loop
455 	int8 maxRedirs = fOptMaxRedirs;
456 	bool newRequest;
457 
458 	do {
459 		newRequest = false;
460 
461 		// Result reset
462 		fHeaders.Clear();
463 		_ResultHeaders().Clear();
464 
465 		BString host = fUrl.Host();
466 		int port = fSSL ? 443 : 80;
467 
468 		if (fUrl.HasPort())
469 			port = fUrl.Port();
470 
471 		if (fContext->UseProxy()) {
472 			host = fContext->GetProxyHost();
473 			port = fContext->GetProxyPort();
474 		}
475 
476 		status_t result = fInputBuffer.InitCheck();
477 		if (result != B_OK)
478 			return result;
479 
480 		if (!_ResolveHostName(host, port)) {
481 			_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
482 				"Unable to resolve hostname (%s), aborting.",
483 					fUrl.Host().String());
484 			return B_SERVER_NOT_FOUND;
485 		}
486 
487 		status_t requestStatus = _MakeRequest();
488 		if (requestStatus != B_OK)
489 			return requestStatus;
490 
491 		// Prepare the referer for the next request if needed
492 		if (fOptAutoReferer)
493 			fOptReferer = fUrl.UrlString();
494 
495 		switch (StatusCodeClass(fResult.StatusCode())) {
496 			case B_HTTP_STATUS_CLASS_INFORMATIONAL:
497 				// Header 100:continue should have been
498 				// handled in the _MakeRequest read loop
499 				break;
500 
501 			case B_HTTP_STATUS_CLASS_SUCCESS:
502 				break;
503 
504 			case B_HTTP_STATUS_CLASS_REDIRECTION:
505 			{
506 				// Redirection has been explicitly disabled
507 				if (!fOptFollowLocation)
508 					break;
509 
510 				int code = fResult.StatusCode();
511 				if (code == B_HTTP_STATUS_MOVED_PERMANENTLY
512 					|| code == B_HTTP_STATUS_FOUND
513 					|| code == B_HTTP_STATUS_SEE_OTHER
514 					|| code == B_HTTP_STATUS_TEMPORARY_REDIRECT) {
515 					BString locationUrl = fHeaders["Location"];
516 
517 					fUrl = BUrl(fUrl, locationUrl);
518 
519 					// 302 and 303 redirections also convert POST requests to GET
520 					// (and remove the posted form data)
521 					if ((code == B_HTTP_STATUS_FOUND
522 						|| code == B_HTTP_STATUS_SEE_OTHER)
523 						&& fRequestMethod == B_HTTP_POST) {
524 						SetMethod(B_HTTP_GET);
525 						delete fOptPostFields;
526 						fOptPostFields = NULL;
527 						delete fOptInputData;
528 						fOptInputData = NULL;
529 						fOptInputDataSize = 0;
530 					}
531 
532 					if (--maxRedirs > 0) {
533 						newRequest = true;
534 
535 						// Redirections may need a switch from http to https.
536 						if (fUrl.Protocol() == "https")
537 							fSSL = true;
538 						else if (fUrl.Protocol() == "http")
539 							fSSL = false;
540 
541 						_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
542 							"Following: %s\n",
543 							fUrl.UrlString().String());
544 					}
545 				}
546 				break;
547 			}
548 
549 			case B_HTTP_STATUS_CLASS_CLIENT_ERROR:
550 				if (fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) {
551 					BHttpAuthentication* authentication
552 						= &fContext->GetAuthentication(fUrl);
553 					status_t status = B_OK;
554 
555 					if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) {
556 						// There is no authentication context for this
557 						// url yet, so let's create one.
558 						BHttpAuthentication newAuth;
559 						newAuth.Initialize(fHeaders["WWW-Authenticate"]);
560 						fContext->AddAuthentication(fUrl, newAuth);
561 
562 						// Get the copy of the authentication we just added.
563 						// That copy is owned by the BUrlContext and won't be
564 						// deleted (unlike the temporary object above)
565 						authentication = &fContext->GetAuthentication(fUrl);
566 					}
567 
568 					newRequest = false;
569 					if (fOptUsername.Length() > 0 && status == B_OK) {
570 						// If we received an username and password, add them
571 						// to the request. This will either change the
572 						// credentials for an existing request, or set them
573 						// for a new one we created just above.
574 						//
575 						// If this request handles HTTP redirections, it will
576 						// also automatically retry connecting and send the
577 						// login information.
578 						authentication->SetUserName(fOptUsername);
579 						authentication->SetPassword(fOptPassword);
580 						newRequest = true;
581 					}
582 				}
583 				break;
584 
585 			case B_HTTP_STATUS_CLASS_SERVER_ERROR:
586 				break;
587 
588 			default:
589 			case B_HTTP_STATUS_CLASS_INVALID:
590 				break;
591 		}
592 	} while (newRequest);
593 
594 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
595 		"%ld headers and %ld bytes of data remaining",
596 		fHeaders.CountHeaders(), fInputBuffer.Size());
597 
598 	if (fResult.StatusCode() == 404)
599 		return B_RESOURCE_NOT_FOUND;
600 
601 	return B_OK;
602 }
603 
604 
605 #ifdef LIBNETAPI_DEPRECATED
606 status_t
607 BHttpRequest::_MakeRequest()
608 {
609 	delete fSocket;
610 
611 	if (fSSL) {
612 		if (fContext->UseProxy()) {
613 			BNetworkAddress proxy(fContext->GetProxyHost(), fContext->GetProxyPort());
614 			fSocket = new(std::nothrow) BPrivate::CheckedProxySecureSocket(proxy, this);
615 		} else
616 			fSocket = new(std::nothrow) BPrivate::CheckedSecureSocket(this);
617 	} else
618 		fSocket = new(std::nothrow) BSocket();
619 
620 	if (fSocket == NULL)
621 		return B_NO_MEMORY;
622 
623 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
624 		fUrl.Authority().String(), fRemoteAddr.Port());
625 	status_t connectError = fSocket->Connect(fRemoteAddr);
626 
627 	if (connectError != B_OK) {
628 		_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s",
629 			strerror(connectError));
630 		return connectError;
631 	}
632 
633 	//! ProtocolHook:ConnectionOpened
634 	if (fListener != NULL)
635 		fListener->ConnectionOpened(this);
636 
637 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
638 		"Connection opened, sending request.");
639 
640 	BString requestHeaders;
641 	requestHeaders.Append(_SerializeRequest());
642 	requestHeaders.Append(_SerializeHeaders());
643 	requestHeaders.Append("\r\n");
644 	fSocket->Write(requestHeaders.String(), requestHeaders.Length());
645 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
646 
647 	_SendPostData();
648 	fRequestStatus = kRequestInitialState;
649 
650 
651 
652 	// Receive loop
653 	bool disableListener = false;
654 	bool receiveEnd = false;
655 	bool parseEnd = false;
656 	bool readByChunks = false;
657 	bool decompress = false;
658 	status_t readError = B_OK;
659 	ssize_t bytesRead = 0;
660 	off_t bytesReceived = 0;
661 	off_t bytesTotal = 0;
662 	size_t previousBufferSize = 0;
663 	off_t bytesUnpacked = 0;
664 	char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize];
665 	ArrayDeleter<char> inputTempBufferDeleter(inputTempBuffer);
666 	ssize_t inputTempSize = kHttpBufferSize;
667 	ssize_t chunkSize = -1;
668 	DynamicBuffer decompressorStorage;
669 	BDataIO* decompressingStream;
670 	ObjectDeleter<BDataIO> decompressingStreamDeleter;
671 
672 	while (!fQuit && !(receiveEnd && parseEnd)) {
673 		if ((!receiveEnd) && (fInputBuffer.Size() == previousBufferSize)) {
674 			BStackOrHeapArray<char, 4096> chunk(kHttpBufferSize);
675 			bytesRead = fSocket->Read(chunk, kHttpBufferSize);
676 
677 			if (bytesRead < 0) {
678 				readError = bytesRead;
679 				break;
680 			} else if (bytesRead == 0) {
681 				// Check if we got the expected number of bytes.
682 				// Exceptions:
683 				// - If the content-length is not known (bytesTotal is 0), for
684 				//   example in the case of a chunked transfer, we can't know
685 				// - If the request method is "HEAD" which explicitly asks the
686 				//   server to not send any data (only the headers)
687 				if (bytesTotal > 0 && bytesReceived != bytesTotal) {
688 					readError = B_IO_ERROR;
689 					break;
690 				}
691 				receiveEnd = true;
692 			}
693 
694 			fInputBuffer.AppendData(chunk, bytesRead);
695 		} else
696 			bytesRead = 0;
697 
698 		previousBufferSize = fInputBuffer.Size();
699 
700 		if (fRequestStatus < kRequestStatusReceived) {
701 			_ParseStatus();
702 
703 			//! ProtocolHook:ResponseStarted
704 			if (fRequestStatus >= kRequestStatusReceived && fListener != NULL
705 					&& !disableListener)
706 				fListener->ResponseStarted(this);
707 		}
708 
709 		if (fRequestStatus < kRequestHeadersReceived) {
710 			_ParseHeaders();
711 
712 			if (fRequestStatus >= kRequestHeadersReceived) {
713 				_ResultHeaders() = fHeaders;
714 
715 				// Parse received cookies
716 				if (fContext != NULL) {
717 					for (int32 i = 0;  i < fHeaders.CountHeaders(); i++) {
718 						if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
719 							fContext->GetCookieJar().AddCookie(
720 								fHeaders.HeaderAt(i).Value(), fUrl);
721 						}
722 					}
723 				}
724 
725 				//! ProtocolHook:HeadersReceived
726 				if (fListener != NULL && !disableListener)
727 					fListener->HeadersReceived(this, fResult);
728 
729 
730 				if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
731 					readByChunks = true;
732 
733 				BString contentEncoding(fHeaders["Content-Encoding"]);
734 				// We don't advertise "deflate" support (see above), but we
735 				// still try to decompress it, if a server ever sends a deflate
736 				// stream despite it not being in our Accept-Encoding list.
737 				if (contentEncoding == "gzip"
738 						|| contentEncoding == "deflate") {
739 					decompress = true;
740 					readError = BZlibCompressionAlgorithm()
741 						.CreateDecompressingOutputStream(&decompressorStorage,
742 							NULL, decompressingStream);
743 					if (readError != B_OK)
744 						break;
745 
746 					decompressingStreamDeleter.SetTo(decompressingStream);
747 				}
748 
749 				int32 index = fHeaders.HasHeader("Content-Length");
750 				if (index != B_ERROR)
751 					bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
752 				else
753 					bytesTotal = -1;
754 
755 				if (fRequestMethod == B_HTTP_HEAD
756 					|| fResult.StatusCode() == 204) {
757 					// In the case of a HEAD request or if the server replies
758 					// 204 ("no content"), we don't expect to receive anything
759 					// more, and the socket will be closed.
760 					receiveEnd = true;
761 				}
762 			}
763 		}
764 
765 		if (fRequestStatus >= kRequestHeadersReceived) {
766 			// If Transfer-Encoding is chunked, we should read a complete
767 			// chunk in buffer before handling it
768 			if (readByChunks) {
769 				if (chunkSize >= 0) {
770 					if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
771 							// 2 more bytes to handle the closing CR+LF
772 						bytesRead = chunkSize;
773 						if (inputTempSize < chunkSize + 2) {
774 							inputTempSize = chunkSize + 2;
775 							inputTempBuffer
776 								= new(std::nothrow) char[inputTempSize];
777 							inputTempBufferDeleter.SetTo(inputTempBuffer);
778 						}
779 
780 						if (inputTempBuffer == NULL) {
781 							readError = B_NO_MEMORY;
782 							break;
783 						}
784 
785 						fInputBuffer.RemoveData(inputTempBuffer,
786 							chunkSize + 2);
787 						chunkSize = -1;
788 					} else {
789 						// Not enough data, try again later
790 						bytesRead = -1;
791 					}
792 				} else {
793 					BString chunkHeader;
794 					if (_GetLine(chunkHeader) == B_ERROR) {
795 						chunkSize = -1;
796 						bytesRead = -1;
797 					} else {
798 						// Format of a chunk header:
799 						// <chunk size in hex>[; optional data]
800 						int32 semiColonIndex = chunkHeader.FindFirst(';', 0);
801 
802 						// Cut-off optional data if present
803 						if (semiColonIndex != -1) {
804 							chunkHeader.Remove(semiColonIndex,
805 								chunkHeader.Length() - semiColonIndex);
806 						}
807 
808 						chunkSize = strtol(chunkHeader.String(), NULL, 16);
809 						if (chunkSize == 0)
810 							fRequestStatus = kRequestContentReceived;
811 
812 						bytesRead = -1;
813 					}
814 				}
815 
816 				// A chunk of 0 bytes indicates the end of the chunked transfer
817 				if (bytesRead == 0)
818 					receiveEnd = true;
819 			} else {
820 				bytesRead = fInputBuffer.Size();
821 
822 				if (bytesRead > 0) {
823 					if (inputTempSize < bytesRead) {
824 						inputTempSize = bytesRead;
825 						inputTempBuffer = new(std::nothrow) char[bytesRead];
826 						inputTempBufferDeleter.SetTo(inputTempBuffer);
827 					}
828 
829 					if (inputTempBuffer == NULL) {
830 						readError = B_NO_MEMORY;
831 						break;
832 					}
833 					fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
834 				}
835 			}
836 
837 			if (bytesRead >= 0) {
838 				bytesReceived += bytesRead;
839 
840 				if (fListener != NULL && !disableListener) {
841 					if (decompress) {
842 						readError = decompressingStream->WriteExactly(
843 							inputTempBuffer, bytesRead);
844 						if (readError != B_OK)
845 							break;
846 
847 						ssize_t size = decompressorStorage.Size();
848 						BStackOrHeapArray<char, 4096> buffer(size);
849 						size = decompressorStorage.Read(buffer, size);
850 						_NotifyDataReceived(buffer, bytesUnpacked, size,
851 							bytesReceived, bytesTotal);
852 						bytesUnpacked += size;
853 					} else if (bytesRead > 0) {
854 						_NotifyDataReceived(inputTempBuffer,
855 							bytesReceived - bytesRead, bytesRead,
856 							bytesReceived, bytesTotal);
857 					}
858 				}
859 
860 				if (bytesTotal >= 0 && bytesReceived >= bytesTotal)
861 					receiveEnd = true;
862 
863 				if (decompress && receiveEnd && !disableListener) {
864 					readError = decompressingStream->Flush();
865 
866 					if (readError == B_BUFFER_OVERFLOW)
867 						readError = B_OK;
868 
869 					if (readError != B_OK)
870 						break;
871 
872 					ssize_t size = decompressorStorage.Size();
873 					BStackOrHeapArray<char, 4096> buffer(size);
874 					size = decompressorStorage.Read(buffer, size);
875 					if (fListener != NULL) {
876 						_NotifyDataReceived(buffer, bytesUnpacked, size,
877 							bytesReceived, bytesTotal);
878 						bytesUnpacked += size;
879 					}
880 				}
881 			}
882 		}
883 
884 		parseEnd = (fInputBuffer.Size() == 0);
885 	}
886 
887 	fSocket->Disconnect();
888 
889 	if (readError != B_OK)
890 		return readError;
891 
892 	return fQuit ? B_INTERRUPTED : B_OK;
893 }
894 
895 #else
896 
897 status_t
898 BHttpRequest::_MakeRequest()
899 {
900 	delete fSocket;
901 
902 	if (fSSL) {
903 		if (fContext->UseProxy()) {
904 			BNetworkAddress proxy(fContext->GetProxyHost(), fContext->GetProxyPort());
905 			fSocket = new(std::nothrow) BPrivate::CheckedProxySecureSocket(proxy, this);
906 		} else
907 			fSocket = new(std::nothrow) BPrivate::CheckedSecureSocket(this);
908 	} else
909 		fSocket = new(std::nothrow) BSocket();
910 
911 	if (fSocket == NULL)
912 		return B_NO_MEMORY;
913 
914 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
915 		fUrl.Authority().String(), fRemoteAddr.Port());
916 	status_t connectError = fSocket->Connect(fRemoteAddr);
917 
918 	if (connectError != B_OK) {
919 		_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s",
920 			strerror(connectError));
921 		return connectError;
922 	}
923 
924 	//! ProtocolHook:ConnectionOpened
925 	if (fListener != NULL)
926 		fListener->ConnectionOpened(this);
927 
928 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
929 		"Connection opened, sending request.");
930 
931 	BString requestHeaders;
932 	requestHeaders.Append(_SerializeRequest());
933 	requestHeaders.Append(_SerializeHeaders());
934 	requestHeaders.Append("\r\n");
935 	fSocket->Write(requestHeaders.String(), requestHeaders.Length());
936 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
937 
938 	_SendPostData();
939 	fRequestStatus = kRequestInitialState;
940 
941 
942 
943 	// Receive loop
944 	bool disableListener = false;
945 	bool receiveEnd = false;
946 	bool parseEnd = false;
947 	bool readByChunks = false;
948 	bool decompress = false;
949 	status_t readError = B_OK;
950 	ssize_t bytesRead = 0;
951 	off_t bytesReceived = 0;
952 	off_t bytesTotal = 0;
953 	size_t previousBufferSize = 0;
954 	off_t bytesUnpacked = 0;
955 	char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize];
956 	ArrayDeleter<char> inputTempBufferDeleter(inputTempBuffer);
957 	ssize_t inputTempSize = kHttpBufferSize;
958 	ssize_t chunkSize = -1;
959 	DynamicBuffer decompressorStorage;
960 	BDataIO* decompressingStream;
961 	ObjectDeleter<BDataIO> decompressingStreamDeleter;
962 
963 	while (!fQuit && !(receiveEnd && parseEnd)) {
964 		if ((!receiveEnd) && (fInputBuffer.Size() == previousBufferSize)) {
965 			BStackOrHeapArray<char, 4096> chunk(kHttpBufferSize);
966 			bytesRead = fSocket->Read(chunk, kHttpBufferSize);
967 
968 			if (bytesRead < 0) {
969 				readError = bytesRead;
970 				break;
971 			} else if (bytesRead == 0) {
972 				// Check if we got the expected number of bytes.
973 				// Exceptions:
974 				// - If the content-length is not known (bytesTotal is 0), for
975 				//   example in the case of a chunked transfer, we can't know
976 				// - If the request method is "HEAD" which explicitly asks the
977 				//   server to not send any data (only the headers)
978 				if (bytesTotal > 0 && bytesReceived != bytesTotal) {
979 					readError = B_IO_ERROR;
980 					break;
981 				}
982 				receiveEnd = true;
983 			}
984 
985 			fInputBuffer.AppendData(chunk, bytesRead);
986 		} else
987 			bytesRead = 0;
988 
989 		previousBufferSize = fInputBuffer.Size();
990 
991 		if (fRequestStatus < kRequestStatusReceived) {
992 			_ParseStatus();
993 
994 			if (fOptFollowLocation
995 					&& IsRedirectionStatusCode(fResult.StatusCode()))
996 				disableListener = true;
997 
998 			if (fOptStopOnError
999 					&& fResult.StatusCode() >= B_HTTP_STATUS_CLASS_CLIENT_ERROR)
1000 			{
1001 				fQuit = true;
1002 				break;
1003 			}
1004 
1005 			//! ProtocolHook:ResponseStarted
1006 			if (fRequestStatus >= kRequestStatusReceived && fListener != NULL
1007 					&& !disableListener)
1008 				fListener->ResponseStarted(this);
1009 		}
1010 
1011 		if (fRequestStatus < kRequestHeadersReceived) {
1012 			_ParseHeaders();
1013 
1014 			if (fRequestStatus >= kRequestHeadersReceived) {
1015 				_ResultHeaders() = fHeaders;
1016 
1017 				// Parse received cookies
1018 				if (fContext != NULL) {
1019 					for (int32 i = 0;  i < fHeaders.CountHeaders(); i++) {
1020 						if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
1021 							fContext->GetCookieJar().AddCookie(
1022 								fHeaders.HeaderAt(i).Value(), fUrl);
1023 						}
1024 					}
1025 				}
1026 
1027 				//! ProtocolHook:HeadersReceived
1028 				if (fListener != NULL && !disableListener)
1029 					fListener->HeadersReceived(this);
1030 
1031 
1032 				if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
1033 					readByChunks = true;
1034 
1035 				BString contentEncoding(fHeaders["Content-Encoding"]);
1036 				// We don't advertise "deflate" support (see above), but we
1037 				// still try to decompress it, if a server ever sends a deflate
1038 				// stream despite it not being in our Accept-Encoding list.
1039 				if (contentEncoding == "gzip"
1040 						|| contentEncoding == "deflate") {
1041 					decompress = true;
1042 					readError = BZlibCompressionAlgorithm()
1043 						.CreateDecompressingOutputStream(&decompressorStorage,
1044 							NULL, decompressingStream);
1045 					if (readError != B_OK)
1046 						break;
1047 
1048 					decompressingStreamDeleter.SetTo(decompressingStream);
1049 				}
1050 
1051 				int32 index = fHeaders.HasHeader("Content-Length");
1052 				if (index != B_ERROR)
1053 					bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
1054 				else
1055 					bytesTotal = -1;
1056 
1057 				if (fRequestMethod == B_HTTP_HEAD
1058 					|| fResult.StatusCode() == 204) {
1059 					// In the case of a HEAD request or if the server replies
1060 					// 204 ("no content"), we don't expect to receive anything
1061 					// more, and the socket will be closed.
1062 					receiveEnd = true;
1063 				}
1064 			}
1065 		}
1066 
1067 		if (fRequestStatus >= kRequestHeadersReceived) {
1068 			// If Transfer-Encoding is chunked, we should read a complete
1069 			// chunk in buffer before handling it
1070 			if (readByChunks) {
1071 				if (chunkSize >= 0) {
1072 					if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
1073 							// 2 more bytes to handle the closing CR+LF
1074 						bytesRead = chunkSize;
1075 						if (inputTempSize < chunkSize + 2) {
1076 							inputTempSize = chunkSize + 2;
1077 							inputTempBuffer
1078 								= new(std::nothrow) char[inputTempSize];
1079 							inputTempBufferDeleter.SetTo(inputTempBuffer);
1080 						}
1081 
1082 						if (inputTempBuffer == NULL) {
1083 							readError = B_NO_MEMORY;
1084 							break;
1085 						}
1086 
1087 						fInputBuffer.RemoveData(inputTempBuffer,
1088 							chunkSize + 2);
1089 						chunkSize = -1;
1090 					} else {
1091 						// Not enough data, try again later
1092 						bytesRead = -1;
1093 					}
1094 				} else {
1095 					BString chunkHeader;
1096 					if (_GetLine(chunkHeader) == B_ERROR) {
1097 						chunkSize = -1;
1098 						bytesRead = -1;
1099 					} else {
1100 						// Format of a chunk header:
1101 						// <chunk size in hex>[; optional data]
1102 						int32 semiColonIndex = chunkHeader.FindFirst(';', 0);
1103 
1104 						// Cut-off optional data if present
1105 						if (semiColonIndex != -1) {
1106 							chunkHeader.Remove(semiColonIndex,
1107 								chunkHeader.Length() - semiColonIndex);
1108 						}
1109 
1110 						chunkSize = strtol(chunkHeader.String(), NULL, 16);
1111 						if (chunkSize == 0)
1112 							fRequestStatus = kRequestContentReceived;
1113 
1114 						bytesRead = -1;
1115 					}
1116 				}
1117 
1118 				// A chunk of 0 bytes indicates the end of the chunked transfer
1119 				if (bytesRead == 0)
1120 					receiveEnd = true;
1121 			} else {
1122 				bytesRead = fInputBuffer.Size();
1123 
1124 				if (bytesRead > 0) {
1125 					if (inputTempSize < bytesRead) {
1126 						inputTempSize = bytesRead;
1127 						inputTempBuffer = new(std::nothrow) char[bytesRead];
1128 						inputTempBufferDeleter.SetTo(inputTempBuffer);
1129 					}
1130 
1131 					if (inputTempBuffer == NULL) {
1132 						readError = B_NO_MEMORY;
1133 						break;
1134 					}
1135 					fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
1136 				}
1137 			}
1138 
1139 			if (bytesRead >= 0) {
1140 				bytesReceived += bytesRead;
1141 
1142 				if (fOutput != NULL && !disableListener) {
1143 					if (decompress) {
1144 						readError = decompressingStream->WriteExactly(
1145 							inputTempBuffer, bytesRead);
1146 						if (readError != B_OK)
1147 							break;
1148 
1149 						ssize_t size = decompressorStorage.Size();
1150 						BStackOrHeapArray<char, 4096> buffer(size);
1151 						size = decompressorStorage.Read(buffer, size);
1152 						if (size > 0) {
1153 							size_t written = 0;
1154 							readError = fOutput->WriteExactly(buffer,
1155 								size, &written);
1156 							if (fListener != NULL && written > 0)
1157 								fListener->BytesWritten(this, written);
1158 							if (readError != B_OK)
1159 								break;
1160 							bytesUnpacked += size;
1161 						}
1162 					} else if (bytesRead > 0) {
1163 						size_t written = 0;
1164 						readError = fOutput->WriteExactly(inputTempBuffer,
1165 							bytesRead, &written);
1166 						if (fListener != NULL && written > 0)
1167 							fListener->BytesWritten(this, written);
1168 						if (readError != B_OK)
1169 							break;
1170 					}
1171 				}
1172 
1173 				if (fListener != NULL && !disableListener)
1174 					fListener->DownloadProgress(this, bytesReceived,
1175 						std::max((off_t)0, bytesTotal));
1176 
1177 				if (bytesTotal >= 0 && bytesReceived >= bytesTotal)
1178 					receiveEnd = true;
1179 
1180 				if (decompress && receiveEnd && !disableListener) {
1181 					readError = decompressingStream->Flush();
1182 
1183 					if (readError == B_BUFFER_OVERFLOW)
1184 						readError = B_OK;
1185 
1186 					if (readError != B_OK)
1187 						break;
1188 
1189 					ssize_t size = decompressorStorage.Size();
1190 					BStackOrHeapArray<char, 4096> buffer(size);
1191 					size = decompressorStorage.Read(buffer, size);
1192 					if (fOutput != NULL && size > 0 && !disableListener) {
1193 						size_t written = 0;
1194 						readError = fOutput->WriteExactly(buffer, size,
1195 							&written);
1196 						if (fListener != NULL && written > 0)
1197 							fListener->BytesWritten(this, written);
1198 						if (readError != B_OK)
1199 							break;
1200 						bytesUnpacked += size;
1201 					}
1202 				}
1203 			}
1204 		}
1205 
1206 		parseEnd = (fInputBuffer.Size() == 0);
1207 	}
1208 
1209 	fSocket->Disconnect();
1210 
1211 	if (readError != B_OK)
1212 		return readError;
1213 
1214 	return fQuit ? B_INTERRUPTED : B_OK;
1215 }
1216 #endif // LIBNETAPI_DEPRECATED
1217 
1218 
1219 void
1220 BHttpRequest::_ParseStatus()
1221 {
1222 	// Status line should be formatted like: HTTP/M.m SSS ...
1223 	// With:   M = Major version of the protocol
1224 	//         m = Minor version of the protocol
1225 	//       SSS = three-digit status code of the response
1226 	//       ... = additional text info
1227 	BString statusLine;
1228 	if (_GetLine(statusLine) == B_ERROR)
1229 		return;
1230 
1231 	if (statusLine.CountChars() < 12)
1232 		return;
1233 
1234 	fRequestStatus = kRequestStatusReceived;
1235 
1236 	BString statusCodeStr;
1237 	BString statusText;
1238 	statusLine.CopyInto(statusCodeStr, 9, 3);
1239 	_SetResultStatusCode(atoi(statusCodeStr.String()));
1240 
1241 	statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13);
1242 
1243 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)",
1244 		atoi(statusCodeStr.String()), _ResultStatusText().String());
1245 }
1246 
1247 
1248 void
1249 BHttpRequest::_ParseHeaders()
1250 {
1251 	BString currentHeader;
1252 	while (_GetLine(currentHeader) != B_ERROR) {
1253 		// An empty line means the end of the header section
1254 		if (currentHeader.Length() == 0) {
1255 			fRequestStatus = kRequestHeadersReceived;
1256 			return;
1257 		}
1258 
1259 		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s",
1260 			currentHeader.String());
1261 		fHeaders.AddHeader(currentHeader.String());
1262 	}
1263 }
1264 
1265 
1266 BString
1267 BHttpRequest::_SerializeRequest()
1268 {
1269 	BString request(fRequestMethod);
1270 	request << ' ';
1271 
1272 	if (fContext->UseProxy()) {
1273 		// When there is a proxy, the request must include the host and port so
1274 		// the proxy knows where to send the request.
1275 		request << Url().Protocol() << "://" << Url().Host();
1276 		if (Url().HasPort())
1277 			request << ':' << Url().Port();
1278 	}
1279 
1280 	if (Url().HasPath() && Url().Path().Length() > 0)
1281 		request << Url().Path();
1282 	else
1283 		request << '/';
1284 
1285 	if (Url().HasRequest())
1286 		request << '?' << Url().Request();
1287 
1288 	switch (fHttpVersion) {
1289 		case B_HTTP_11:
1290 			request << " HTTP/1.1\r\n";
1291 			break;
1292 
1293 		default:
1294 		case B_HTTP_10:
1295 			request << " HTTP/1.0\r\n";
1296 			break;
1297 	}
1298 
1299 	_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", request.String());
1300 
1301 	return request;
1302 }
1303 
1304 
1305 BString
1306 BHttpRequest::_SerializeHeaders()
1307 {
1308 	BHttpHeaders outputHeaders;
1309 
1310 	// HTTP 1.1 additional headers
1311 	if (fHttpVersion == B_HTTP_11) {
1312 		BString host = Url().Host();
1313 		if (Url().HasPort() && !_IsDefaultPort())
1314 			host << ':' << Url().Port();
1315 
1316 		outputHeaders.AddHeader("Host", host);
1317 
1318 		outputHeaders.AddHeader("Accept", "*/*");
1319 		outputHeaders.AddHeader("Accept-Encoding", "gzip");
1320 			// Allows the server to compress data using the "gzip" format.
1321 			// "deflate" is not supported, because there are two interpretations
1322 			// of what it means (the RFC and Microsoft products), and we don't
1323 			// want to handle this. Very few websites support only deflate,
1324 			// and most of them will send gzip, or at worst, uncompressed data.
1325 
1326 		outputHeaders.AddHeader("Connection", "close");
1327 			// Let the remote server close the connection after response since
1328 			// we don't handle multiple request on a single connection
1329 	}
1330 
1331 	// Classic HTTP headers
1332 	if (fOptUserAgent.CountChars() > 0)
1333 		outputHeaders.AddHeader("User-Agent", fOptUserAgent.String());
1334 
1335 	if (fOptReferer.CountChars() > 0)
1336 		outputHeaders.AddHeader("Referer", fOptReferer.String());
1337 
1338 	// Optional range requests headers
1339 	if (fOptRangeStart != -1 || fOptRangeEnd != -1) {
1340 		if (fOptRangeStart == -1)
1341 			fOptRangeStart = 0;
1342 		BString range;
1343 		if (fOptRangeEnd != -1) {
1344 			range.SetToFormat("bytes=%" B_PRIdOFF "-%" B_PRIdOFF,
1345 				fOptRangeStart, fOptRangeEnd);
1346 		} else {
1347 			range.SetToFormat("bytes=%" B_PRIdOFF "-", fOptRangeStart);
1348 		}
1349 		outputHeaders.AddHeader("Range", range.String());
1350 	}
1351 
1352 	// Authentication
1353 	if (fContext != NULL) {
1354 		BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl);
1355 		if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
1356 			if (fOptUsername.Length() > 0) {
1357 				authentication.SetUserName(fOptUsername);
1358 				authentication.SetPassword(fOptPassword);
1359 			}
1360 
1361 			BString request(fRequestMethod);
1362 			outputHeaders.AddHeader("Authorization",
1363 				authentication.Authorization(fUrl, request));
1364 		}
1365 	}
1366 
1367 	// Required headers for POST data
1368 	if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
1369 		BString contentType;
1370 
1371 		switch (fOptPostFields->GetFormType()) {
1372 			case B_HTTP_FORM_MULTIPART:
1373 				contentType << "multipart/form-data; boundary="
1374 					<< fOptPostFields->GetMultipartBoundary() << "";
1375 				break;
1376 
1377 			case B_HTTP_FORM_URL_ENCODED:
1378 				contentType << "application/x-www-form-urlencoded";
1379 				break;
1380 		}
1381 
1382 		outputHeaders.AddHeader("Content-Type", contentType);
1383 		outputHeaders.AddHeader("Content-Length",
1384 			fOptPostFields->ContentLength());
1385 	} else if (fOptInputData != NULL
1386 			&& (fRequestMethod == B_HTTP_POST
1387 			|| fRequestMethod == B_HTTP_PUT)) {
1388 		if (fOptInputDataSize >= 0)
1389 			outputHeaders.AddHeader("Content-Length", fOptInputDataSize);
1390 		else
1391 			outputHeaders.AddHeader("Transfer-Encoding", "chunked");
1392 	}
1393 
1394 	// Optional headers specified by the user
1395 	if (fOptHeaders != NULL) {
1396 		for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
1397 				headerIndex++) {
1398 			BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
1399 			int32 replaceIndex = outputHeaders.HasHeader(optHeader.Name());
1400 
1401 			// Add or replace the current option header to the
1402 			// output header list
1403 			if (replaceIndex == -1)
1404 				outputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
1405 			else
1406 				outputHeaders[replaceIndex].SetValue(optHeader.Value());
1407 		}
1408 	}
1409 
1410 	// Context cookies
1411 	if (fOptSetCookies && fContext != NULL) {
1412 		BString cookieString;
1413 
1414 		BNetworkCookieJar::UrlIterator iterator
1415 			= fContext->GetCookieJar().GetUrlIterator(fUrl);
1416 		const BNetworkCookie* cookie = iterator.Next();
1417 		if (cookie != NULL) {
1418 			while (true) {
1419 				cookieString << cookie->RawCookie(false);
1420 				cookie = iterator.Next();
1421 				if (cookie == NULL)
1422 					break;
1423 				cookieString << "; ";
1424 			}
1425 
1426 			outputHeaders.AddHeader("Cookie", cookieString);
1427 		}
1428 	}
1429 
1430 	// Write output headers to output stream
1431 	BString headerData;
1432 
1433 	for (int32 headerIndex = 0; headerIndex < outputHeaders.CountHeaders();
1434 			headerIndex++) {
1435 		const char* header = outputHeaders.HeaderAt(headerIndex).Header();
1436 
1437 		headerData << header;
1438 		headerData << "\r\n";
1439 
1440 		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", header);
1441 	}
1442 
1443 	return headerData;
1444 }
1445 
1446 
1447 void
1448 BHttpRequest::_SendPostData()
1449 {
1450 	if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) {
1451 		if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) {
1452 			BString outputBuffer = fOptPostFields->RawData();
1453 			_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
1454 				"%s", outputBuffer.String());
1455 			fSocket->Write(outputBuffer.String(), outputBuffer.Length());
1456 		} else {
1457 			for (BHttpForm::Iterator it = fOptPostFields->GetIterator();
1458 				const BHttpFormData* currentField = it.Next();
1459 				) {
1460 				_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
1461 					it.MultipartHeader().String());
1462 				fSocket->Write(it.MultipartHeader().String(),
1463 					it.MultipartHeader().Length());
1464 
1465 				switch (currentField->Type()) {
1466 					default:
1467 					case B_HTTPFORM_UNKNOWN:
1468 						ASSERT(0);
1469 						break;
1470 
1471 					case B_HTTPFORM_STRING:
1472 						fSocket->Write(currentField->String().String(),
1473 							currentField->String().Length());
1474 						break;
1475 
1476 					case B_HTTPFORM_FILE:
1477 						{
1478 							BFile upFile(currentField->File().Path(),
1479 								B_READ_ONLY);
1480 							char readBuffer[kHttpBufferSize];
1481 							ssize_t readSize;
1482 							off_t totalSize;
1483 
1484 							if (upFile.GetSize(&totalSize) != B_OK)
1485 								ASSERT(0);
1486 
1487 							readSize = upFile.Read(readBuffer,
1488 								sizeof(readBuffer));
1489 							while (readSize > 0) {
1490 								fSocket->Write(readBuffer, readSize);
1491 								readSize = upFile.Read(readBuffer,
1492 									sizeof(readBuffer));
1493 								fListener->UploadProgress(this, readSize,
1494 									std::max((off_t)0, totalSize));
1495 							}
1496 
1497 							break;
1498 						}
1499 					case B_HTTPFORM_BUFFER:
1500 						fSocket->Write(currentField->Buffer(),
1501 							currentField->BufferSize());
1502 						break;
1503 				}
1504 
1505 				fSocket->Write("\r\n", 2);
1506 			}
1507 
1508 			BString footer = fOptPostFields->GetMultipartFooter();
1509 			fSocket->Write(footer.String(), footer.Length());
1510 		}
1511 	} else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)
1512 		&& fOptInputData != NULL) {
1513 
1514 		// If the input data is seekable, we rewind it for each new request.
1515 		BPositionIO* seekableData
1516 			= dynamic_cast<BPositionIO*>(fOptInputData);
1517 		if (seekableData)
1518 			seekableData->Seek(0, SEEK_SET);
1519 
1520 		for (;;) {
1521 			char outputTempBuffer[kHttpBufferSize];
1522 			ssize_t read = fOptInputData->Read(outputTempBuffer,
1523 				sizeof(outputTempBuffer));
1524 
1525 			if (read <= 0)
1526 				break;
1527 
1528 			if (fOptInputDataSize < 0) {
1529 				// Input data size unknown, so we have to use chunked transfer
1530 				char hexSize[18];
1531 				// The string does not need to be NULL terminated.
1532 				size_t hexLength = snprintf(hexSize, sizeof(hexSize), "%lx\r\n",
1533 					read);
1534 
1535 				fSocket->Write(hexSize, hexLength);
1536 				fSocket->Write(outputTempBuffer, read);
1537 				fSocket->Write("\r\n", 2);
1538 			} else {
1539 				fSocket->Write(outputTempBuffer, read);
1540 			}
1541 		}
1542 
1543 		if (fOptInputDataSize < 0) {
1544 			// Chunked transfer terminating sequence
1545 			fSocket->Write("0\r\n\r\n", 5);
1546 		}
1547 	}
1548 
1549 }
1550 
1551 
1552 BHttpHeaders&
1553 BHttpRequest::_ResultHeaders()
1554 {
1555 	return fResult.fHeaders;
1556 }
1557 
1558 
1559 void
1560 BHttpRequest::_SetResultStatusCode(int32 statusCode)
1561 {
1562 	fResult.fStatusCode = statusCode;
1563 }
1564 
1565 
1566 BString&
1567 BHttpRequest::_ResultStatusText()
1568 {
1569 	return fResult.fStatusString;
1570 }
1571 
1572 
1573 bool
1574 BHttpRequest::_CertificateVerificationFailed(BCertificate& certificate,
1575 	const char* message)
1576 {
1577 	if (fContext->HasCertificateException(certificate))
1578 		return true;
1579 
1580 	if (fListener != NULL
1581 		&& fListener->CertificateVerificationFailed(this, certificate, message)) {
1582 		// User asked us to continue anyway, let's add a temporary exception for this certificate
1583 		fContext->AddCertificateException(certificate);
1584 		return true;
1585 	}
1586 
1587 	return false;
1588 }
1589 
1590 
1591 bool
1592 BHttpRequest::_IsDefaultPort()
1593 {
1594 	if (fSSL && Url().Port() == 443)
1595 		return true;
1596 	if (!fSSL && Url().Port() == 80)
1597 		return true;
1598 	return false;
1599 }
1600 
1601 
1602 #ifdef LIBNETAPI_DEPRECATED
1603 void
1604 BHttpRequest::_NotifyDataReceived(const char* data, off_t pos, ssize_t size,
1605 	off_t bytesReceived, ssize_t bytesTotal)
1606 {
1607 	if (fListener == NULL || size <= 0)
1608 		return;
1609 	if (fOptRangeStart > 0) {
1610 		pos += fOptRangeStart;
1611 		// bytesReceived and bytesTotal refer to the requested range,
1612 		// so that should technically not be adjusted for the range start.
1613 		// For displaying progress to the user, this is not ideal, though.
1614 		// But only for the case where we request the remainder of a file.
1615 		// Range requests can also be used to request any portion of a
1616 		// resource, so not modifying them is technically more correct.
1617 		// We can use a little trick, though: We know when the remainder
1618 		// is requested, because then fOptRangeEnd is -1.
1619 		if (fOptRangeEnd == -1) {
1620 			bytesReceived += fOptRangeStart;
1621 			if (bytesTotal > 0)
1622 				bytesTotal += fOptRangeStart;
1623 		}
1624 	}
1625 	fListener->DataReceived(this, data, pos, size);
1626 	fListener->DownloadProgress(this, bytesReceived,
1627 		std::max((ssize_t)0, bytesTotal));
1628 }
1629 #endif
1630