xref: /haiku/src/kits/network/libnetservices/HttpRequest.cpp (revision 4c8e85b316c35a9161f5a1c50ad70bc91c83a76f)
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 
421 	if (fSocket != NULL) {
422 		fSocket->Disconnect();
423 			// Unlock any pending connect, read or write operation.
424 	}
425 	return BNetworkRequest::Stop();
426 }
427 
428 
429 void
430 BHttpRequest::_ResetOptions()
431 {
432 	delete fOptPostFields;
433 	delete fOptHeaders;
434 
435 	fOptFollowLocation = true;
436 	fOptMaxRedirs = 8;
437 	fOptReferer = "";
438 	fOptUserAgent = "Services Kit (Haiku)";
439 	fOptUsername = "";
440 	fOptPassword = "";
441 	fOptAuthMethods = B_HTTP_AUTHENTICATION_BASIC | B_HTTP_AUTHENTICATION_DIGEST
442 		| B_HTTP_AUTHENTICATION_IE_DIGEST;
443 	fOptHeaders = NULL;
444 	fOptPostFields = NULL;
445 	fOptSetCookies = true;
446 	fOptDiscardData = false;
447 	fOptDisableListener = false;
448 	fOptAutoReferer = true;
449 }
450 
451 
452 status_t
453 BHttpRequest::_ProtocolLoop()
454 {
455 	// Initialize the request redirection loop
456 	int8 maxRedirs = fOptMaxRedirs;
457 	bool newRequest;
458 
459 	do {
460 		newRequest = false;
461 
462 		// Result reset
463 		fHeaders.Clear();
464 		_ResultHeaders().Clear();
465 
466 		BString host = fUrl.Host();
467 		int port = fSSL ? 443 : 80;
468 
469 		if (fUrl.HasPort())
470 			port = fUrl.Port();
471 
472 		if (fContext->UseProxy()) {
473 			host = fContext->GetProxyHost();
474 			port = fContext->GetProxyPort();
475 		}
476 
477 		status_t result = fInputBuffer.InitCheck();
478 		if (result != B_OK)
479 			return result;
480 
481 		if (!_ResolveHostName(host, port)) {
482 			_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
483 				"Unable to resolve hostname (%s), aborting.",
484 					fUrl.Host().String());
485 			return B_SERVER_NOT_FOUND;
486 		}
487 
488 		status_t requestStatus = _MakeRequest();
489 		if (requestStatus != B_OK)
490 			return requestStatus;
491 
492 		// Prepare the referer for the next request if needed
493 		if (fOptAutoReferer)
494 			fOptReferer = fUrl.UrlString();
495 
496 		switch (StatusCodeClass(fResult.StatusCode())) {
497 			case B_HTTP_STATUS_CLASS_INFORMATIONAL:
498 				// Header 100:continue should have been
499 				// handled in the _MakeRequest read loop
500 				break;
501 
502 			case B_HTTP_STATUS_CLASS_SUCCESS:
503 				break;
504 
505 			case B_HTTP_STATUS_CLASS_REDIRECTION:
506 			{
507 				// Redirection has been explicitly disabled
508 				if (!fOptFollowLocation)
509 					break;
510 
511 				int code = fResult.StatusCode();
512 				if (code == B_HTTP_STATUS_MOVED_PERMANENTLY
513 					|| code == B_HTTP_STATUS_FOUND
514 					|| code == B_HTTP_STATUS_SEE_OTHER
515 					|| code == B_HTTP_STATUS_TEMPORARY_REDIRECT) {
516 					BString locationUrl = fHeaders["Location"];
517 
518 					fUrl = BUrl(fUrl, locationUrl);
519 
520 					// 302 and 303 redirections also convert POST requests to GET
521 					// (and remove the posted form data)
522 					if ((code == B_HTTP_STATUS_FOUND
523 						|| code == B_HTTP_STATUS_SEE_OTHER)
524 						&& fRequestMethod == B_HTTP_POST) {
525 						SetMethod(B_HTTP_GET);
526 						delete fOptPostFields;
527 						fOptPostFields = NULL;
528 						delete fOptInputData;
529 						fOptInputData = NULL;
530 						fOptInputDataSize = 0;
531 					}
532 
533 					if (--maxRedirs > 0) {
534 						newRequest = true;
535 
536 						// Redirections may need a switch from http to https.
537 						if (fUrl.Protocol() == "https")
538 							fSSL = true;
539 						else if (fUrl.Protocol() == "http")
540 							fSSL = false;
541 
542 						_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
543 							"Following: %s\n",
544 							fUrl.UrlString().String());
545 					}
546 				}
547 				break;
548 			}
549 
550 			case B_HTTP_STATUS_CLASS_CLIENT_ERROR:
551 				if (fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) {
552 					BHttpAuthentication* authentication
553 						= &fContext->GetAuthentication(fUrl);
554 					status_t status = B_OK;
555 
556 					if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) {
557 						// There is no authentication context for this
558 						// url yet, so let's create one.
559 						BHttpAuthentication newAuth;
560 						newAuth.Initialize(fHeaders["WWW-Authenticate"]);
561 						fContext->AddAuthentication(fUrl, newAuth);
562 
563 						// Get the copy of the authentication we just added.
564 						// That copy is owned by the BUrlContext and won't be
565 						// deleted (unlike the temporary object above)
566 						authentication = &fContext->GetAuthentication(fUrl);
567 					}
568 
569 					newRequest = false;
570 					if (fOptUsername.Length() > 0 && status == B_OK) {
571 						// If we received an username and password, add them
572 						// to the request. This will either change the
573 						// credentials for an existing request, or set them
574 						// for a new one we created just above.
575 						//
576 						// If this request handles HTTP redirections, it will
577 						// also automatically retry connecting and send the
578 						// login information.
579 						authentication->SetUserName(fOptUsername);
580 						authentication->SetPassword(fOptPassword);
581 						newRequest = true;
582 					}
583 				}
584 				break;
585 
586 			case B_HTTP_STATUS_CLASS_SERVER_ERROR:
587 				break;
588 
589 			default:
590 			case B_HTTP_STATUS_CLASS_INVALID:
591 				break;
592 		}
593 	} while (newRequest);
594 
595 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
596 		"%" B_PRId32 " headers and %" B_PRIuSIZE " bytes of data remaining",
597 		fHeaders.CountHeaders(), fInputBuffer.Size());
598 
599 	if (fResult.StatusCode() == 404)
600 		return B_RESOURCE_NOT_FOUND;
601 
602 	return B_OK;
603 }
604 
605 
606 #ifdef LIBNETAPI_DEPRECATED
607 status_t
608 BHttpRequest::_MakeRequest()
609 {
610 	delete fSocket;
611 
612 	if (fSSL) {
613 		if (fContext->UseProxy()) {
614 			BNetworkAddress proxy(fContext->GetProxyHost(), fContext->GetProxyPort());
615 			fSocket = new(std::nothrow) BPrivate::CheckedProxySecureSocket(proxy, this);
616 		} else
617 			fSocket = new(std::nothrow) BPrivate::CheckedSecureSocket(this);
618 	} else
619 		fSocket = new(std::nothrow) BSocket();
620 
621 	if (fSocket == NULL)
622 		return B_NO_MEMORY;
623 
624 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
625 		fUrl.Authority().String(), fRemoteAddr.Port());
626 	status_t connectError = fSocket->Connect(fRemoteAddr);
627 
628 	if (connectError != B_OK) {
629 		_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s",
630 			strerror(connectError));
631 		return connectError;
632 	}
633 
634 	//! ProtocolHook:ConnectionOpened
635 	if (fListener != NULL)
636 		fListener->ConnectionOpened(this);
637 
638 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
639 		"Connection opened, sending request.");
640 
641 	BString requestHeaders;
642 	requestHeaders.Append(_SerializeRequest());
643 	requestHeaders.Append(_SerializeHeaders());
644 	requestHeaders.Append("\r\n");
645 	fSocket->Write(requestHeaders.String(), requestHeaders.Length());
646 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
647 
648 	_SendPostData();
649 	fRequestStatus = kRequestInitialState;
650 
651 
652 
653 	// Receive loop
654 	bool disableListener = false;
655 	bool receiveEnd = false;
656 	bool parseEnd = false;
657 	bool readByChunks = false;
658 	bool decompress = false;
659 	status_t readError = B_OK;
660 	ssize_t bytesRead = 0;
661 	off_t bytesReceived = 0;
662 	off_t bytesTotal = 0;
663 	size_t previousBufferSize = 0;
664 	off_t bytesUnpacked = 0;
665 	char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize];
666 	ArrayDeleter<char> inputTempBufferDeleter(inputTempBuffer);
667 	ssize_t inputTempSize = kHttpBufferSize;
668 	ssize_t chunkSize = -1;
669 	DynamicBuffer decompressorStorage;
670 	BDataIO* decompressingStream;
671 	ObjectDeleter<BDataIO> decompressingStreamDeleter;
672 
673 	while (!fQuit && !(receiveEnd && parseEnd)) {
674 		if ((!receiveEnd) && (fInputBuffer.Size() == previousBufferSize)) {
675 			BStackOrHeapArray<char, 4096> chunk(kHttpBufferSize);
676 			bytesRead = fSocket->Read(chunk, kHttpBufferSize);
677 
678 			if (bytesRead < 0) {
679 				readError = bytesRead;
680 				break;
681 			} else if (bytesRead == 0) {
682 				// Check if we got the expected number of bytes.
683 				// Exceptions:
684 				// - If the content-length is not known (bytesTotal is 0), for
685 				//   example in the case of a chunked transfer, we can't know
686 				// - If the request method is "HEAD" which explicitly asks the
687 				//   server to not send any data (only the headers)
688 				if (bytesTotal > 0 && bytesReceived != bytesTotal) {
689 					readError = B_IO_ERROR;
690 					break;
691 				}
692 				receiveEnd = true;
693 			}
694 
695 			fInputBuffer.AppendData(chunk, bytesRead);
696 		} else
697 			bytesRead = 0;
698 
699 		previousBufferSize = fInputBuffer.Size();
700 
701 		if (fRequestStatus < kRequestStatusReceived) {
702 			_ParseStatus();
703 
704 			//! ProtocolHook:ResponseStarted
705 			if (fRequestStatus >= kRequestStatusReceived && fListener != NULL
706 					&& !disableListener)
707 				fListener->ResponseStarted(this);
708 		}
709 
710 		if (fRequestStatus < kRequestHeadersReceived) {
711 			_ParseHeaders();
712 
713 			if (fRequestStatus >= kRequestHeadersReceived) {
714 				_ResultHeaders() = fHeaders;
715 
716 				// Parse received cookies
717 				if (fContext != NULL) {
718 					for (int32 i = 0;  i < fHeaders.CountHeaders(); i++) {
719 						if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
720 							fContext->GetCookieJar().AddCookie(
721 								fHeaders.HeaderAt(i).Value(), fUrl);
722 						}
723 					}
724 				}
725 
726 				//! ProtocolHook:HeadersReceived
727 				if (fListener != NULL && !disableListener)
728 					fListener->HeadersReceived(this, fResult);
729 
730 
731 				if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
732 					readByChunks = true;
733 
734 				BString contentEncoding(fHeaders["Content-Encoding"]);
735 				// We don't advertise "deflate" support (see above), but we
736 				// still try to decompress it, if a server ever sends a deflate
737 				// stream despite it not being in our Accept-Encoding list.
738 				if (contentEncoding == "gzip"
739 						|| contentEncoding == "deflate") {
740 					decompress = true;
741 					readError = BZlibCompressionAlgorithm()
742 						.CreateDecompressingOutputStream(&decompressorStorage,
743 							NULL, decompressingStream);
744 					if (readError != B_OK)
745 						break;
746 
747 					decompressingStreamDeleter.SetTo(decompressingStream);
748 				}
749 
750 				int32 index = fHeaders.HasHeader("Content-Length");
751 				if (index != B_ERROR)
752 					bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
753 				else
754 					bytesTotal = -1;
755 
756 				if (fRequestMethod == B_HTTP_HEAD
757 					|| fResult.StatusCode() == 204) {
758 					// In the case of a HEAD request or if the server replies
759 					// 204 ("no content"), we don't expect to receive anything
760 					// more, and the socket will be closed.
761 					receiveEnd = true;
762 				}
763 			}
764 		}
765 
766 		if (fRequestStatus >= kRequestHeadersReceived) {
767 			// If Transfer-Encoding is chunked, we should read a complete
768 			// chunk in buffer before handling it
769 			if (readByChunks) {
770 				if (chunkSize >= 0) {
771 					if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
772 							// 2 more bytes to handle the closing CR+LF
773 						bytesRead = chunkSize;
774 						if (inputTempSize < chunkSize + 2) {
775 							inputTempSize = chunkSize + 2;
776 							inputTempBuffer
777 								= new(std::nothrow) char[inputTempSize];
778 							inputTempBufferDeleter.SetTo(inputTempBuffer);
779 						}
780 
781 						if (inputTempBuffer == NULL) {
782 							readError = B_NO_MEMORY;
783 							break;
784 						}
785 
786 						fInputBuffer.RemoveData(inputTempBuffer,
787 							chunkSize + 2);
788 						chunkSize = -1;
789 					} else {
790 						// Not enough data, try again later
791 						bytesRead = -1;
792 					}
793 				} else {
794 					BString chunkHeader;
795 					if (_GetLine(chunkHeader) == B_ERROR) {
796 						chunkSize = -1;
797 						bytesRead = -1;
798 					} else {
799 						// Format of a chunk header:
800 						// <chunk size in hex>[; optional data]
801 						int32 semiColonIndex = chunkHeader.FindFirst(';', 0);
802 
803 						// Cut-off optional data if present
804 						if (semiColonIndex != -1) {
805 							chunkHeader.Remove(semiColonIndex,
806 								chunkHeader.Length() - semiColonIndex);
807 						}
808 
809 						chunkSize = strtol(chunkHeader.String(), NULL, 16);
810 						if (chunkSize == 0)
811 							fRequestStatus = kRequestContentReceived;
812 
813 						bytesRead = -1;
814 					}
815 				}
816 
817 				// A chunk of 0 bytes indicates the end of the chunked transfer
818 				if (bytesRead == 0)
819 					receiveEnd = true;
820 			} else {
821 				bytesRead = fInputBuffer.Size();
822 
823 				if (bytesRead > 0) {
824 					if (inputTempSize < bytesRead) {
825 						inputTempSize = bytesRead;
826 						inputTempBuffer = new(std::nothrow) char[bytesRead];
827 						inputTempBufferDeleter.SetTo(inputTempBuffer);
828 					}
829 
830 					if (inputTempBuffer == NULL) {
831 						readError = B_NO_MEMORY;
832 						break;
833 					}
834 					fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
835 				}
836 			}
837 
838 			if (bytesRead >= 0) {
839 				bytesReceived += bytesRead;
840 
841 				if (fListener != NULL && !disableListener) {
842 					if (decompress) {
843 						readError = decompressingStream->WriteExactly(
844 							inputTempBuffer, bytesRead);
845 						if (readError != B_OK)
846 							break;
847 
848 						ssize_t size = decompressorStorage.Size();
849 						BStackOrHeapArray<char, 4096> buffer(size);
850 						size = decompressorStorage.Read(buffer, size);
851 						_NotifyDataReceived(buffer, bytesUnpacked, size,
852 							bytesReceived, bytesTotal);
853 						bytesUnpacked += size;
854 					} else if (bytesRead > 0) {
855 						_NotifyDataReceived(inputTempBuffer,
856 							bytesReceived - bytesRead, bytesRead,
857 							bytesReceived, bytesTotal);
858 					}
859 				}
860 
861 				if (bytesTotal >= 0 && bytesReceived >= bytesTotal)
862 					receiveEnd = true;
863 
864 				if (decompress && receiveEnd && !disableListener) {
865 					readError = decompressingStream->Flush();
866 
867 					if (readError == B_BUFFER_OVERFLOW)
868 						readError = B_OK;
869 
870 					if (readError != B_OK)
871 						break;
872 
873 					ssize_t size = decompressorStorage.Size();
874 					BStackOrHeapArray<char, 4096> buffer(size);
875 					size = decompressorStorage.Read(buffer, size);
876 					if (fListener != NULL) {
877 						_NotifyDataReceived(buffer, bytesUnpacked, size,
878 							bytesReceived, bytesTotal);
879 						bytesUnpacked += size;
880 					}
881 				}
882 			}
883 		}
884 
885 		parseEnd = (fInputBuffer.Size() == 0);
886 	}
887 
888 	fSocket->Disconnect();
889 
890 	if (readError != B_OK)
891 		return readError;
892 
893 	return fQuit ? B_INTERRUPTED : B_OK;
894 }
895 
896 #else
897 
898 status_t
899 BHttpRequest::_MakeRequest()
900 {
901 	delete fSocket;
902 
903 	if (fSSL) {
904 		if (fContext->UseProxy()) {
905 			BNetworkAddress proxy(fContext->GetProxyHost(), fContext->GetProxyPort());
906 			fSocket = new(std::nothrow) BPrivate::CheckedProxySecureSocket(proxy, this);
907 		} else
908 			fSocket = new(std::nothrow) BPrivate::CheckedSecureSocket(this);
909 	} else
910 		fSocket = new(std::nothrow) BSocket();
911 
912 	if (fSocket == NULL)
913 		return B_NO_MEMORY;
914 
915 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
916 		fUrl.Authority().String(), fRemoteAddr.Port());
917 	status_t connectError = fSocket->Connect(fRemoteAddr);
918 
919 	if (connectError != B_OK) {
920 		_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s",
921 			strerror(connectError));
922 		return connectError;
923 	}
924 
925 	//! ProtocolHook:ConnectionOpened
926 	if (fListener != NULL)
927 		fListener->ConnectionOpened(this);
928 
929 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
930 		"Connection opened, sending request.");
931 
932 	BString requestHeaders;
933 	requestHeaders.Append(_SerializeRequest());
934 	requestHeaders.Append(_SerializeHeaders());
935 	requestHeaders.Append("\r\n");
936 	fSocket->Write(requestHeaders.String(), requestHeaders.Length());
937 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");
938 
939 	_SendPostData();
940 	fRequestStatus = kRequestInitialState;
941 
942 
943 
944 	// Receive loop
945 	bool disableListener = false;
946 	bool receiveEnd = false;
947 	bool parseEnd = false;
948 	bool readByChunks = false;
949 	bool decompress = false;
950 	status_t readError = B_OK;
951 	ssize_t bytesRead = 0;
952 	off_t bytesReceived = 0;
953 	off_t bytesTotal = 0;
954 	size_t previousBufferSize = 0;
955 	off_t bytesUnpacked = 0;
956 	char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize];
957 	ArrayDeleter<char> inputTempBufferDeleter(inputTempBuffer);
958 	ssize_t inputTempSize = kHttpBufferSize;
959 	ssize_t chunkSize = -1;
960 	DynamicBuffer decompressorStorage;
961 	BDataIO* decompressingStream;
962 	ObjectDeleter<BDataIO> decompressingStreamDeleter;
963 
964 	while (!fQuit && !(receiveEnd && parseEnd)) {
965 		if ((!receiveEnd) && (fInputBuffer.Size() == previousBufferSize)) {
966 			BStackOrHeapArray<char, 4096> chunk(kHttpBufferSize);
967 			bytesRead = fSocket->Read(chunk, kHttpBufferSize);
968 
969 			if (bytesRead < 0) {
970 				readError = bytesRead;
971 				break;
972 			} else if (bytesRead == 0) {
973 				// Check if we got the expected number of bytes.
974 				// Exceptions:
975 				// - If the content-length is not known (bytesTotal is 0), for
976 				//   example in the case of a chunked transfer, we can't know
977 				// - If the request method is "HEAD" which explicitly asks the
978 				//   server to not send any data (only the headers)
979 				if (bytesTotal > 0 && bytesReceived != bytesTotal) {
980 					readError = B_IO_ERROR;
981 					break;
982 				}
983 				receiveEnd = true;
984 			}
985 
986 			fInputBuffer.AppendData(chunk, bytesRead);
987 		} else
988 			bytesRead = 0;
989 
990 		previousBufferSize = fInputBuffer.Size();
991 
992 		if (fRequestStatus < kRequestStatusReceived) {
993 			_ParseStatus();
994 
995 			if (fOptFollowLocation
996 					&& IsRedirectionStatusCode(fResult.StatusCode()))
997 				disableListener = true;
998 
999 			if (fOptStopOnError
1000 					&& fResult.StatusCode() >= B_HTTP_STATUS_CLASS_CLIENT_ERROR)
1001 			{
1002 				fQuit = true;
1003 				break;
1004 			}
1005 
1006 			//! ProtocolHook:ResponseStarted
1007 			if (fRequestStatus >= kRequestStatusReceived && fListener != NULL
1008 					&& !disableListener)
1009 				fListener->ResponseStarted(this);
1010 		}
1011 
1012 		if (fRequestStatus < kRequestHeadersReceived) {
1013 			_ParseHeaders();
1014 
1015 			if (fRequestStatus >= kRequestHeadersReceived) {
1016 				_ResultHeaders() = fHeaders;
1017 
1018 				// Parse received cookies
1019 				if (fContext != NULL) {
1020 					for (int32 i = 0;  i < fHeaders.CountHeaders(); i++) {
1021 						if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
1022 							fContext->GetCookieJar().AddCookie(
1023 								fHeaders.HeaderAt(i).Value(), fUrl);
1024 						}
1025 					}
1026 				}
1027 
1028 				//! ProtocolHook:HeadersReceived
1029 				if (fListener != NULL && !disableListener)
1030 					fListener->HeadersReceived(this);
1031 
1032 
1033 				if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
1034 					readByChunks = true;
1035 
1036 				BString contentEncoding(fHeaders["Content-Encoding"]);
1037 				// We don't advertise "deflate" support (see above), but we
1038 				// still try to decompress it, if a server ever sends a deflate
1039 				// stream despite it not being in our Accept-Encoding list.
1040 				if (contentEncoding == "gzip"
1041 						|| contentEncoding == "deflate") {
1042 					decompress = true;
1043 					readError = BZlibCompressionAlgorithm()
1044 						.CreateDecompressingOutputStream(&decompressorStorage,
1045 							NULL, decompressingStream);
1046 					if (readError != B_OK)
1047 						break;
1048 
1049 					decompressingStreamDeleter.SetTo(decompressingStream);
1050 				}
1051 
1052 				int32 index = fHeaders.HasHeader("Content-Length");
1053 				if (index != B_ERROR)
1054 					bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
1055 				else
1056 					bytesTotal = -1;
1057 
1058 				if (fRequestMethod == B_HTTP_HEAD
1059 					|| fResult.StatusCode() == 204) {
1060 					// In the case of a HEAD request or if the server replies
1061 					// 204 ("no content"), we don't expect to receive anything
1062 					// more, and the socket will be closed.
1063 					receiveEnd = true;
1064 				}
1065 			}
1066 		}
1067 
1068 		if (fRequestStatus >= kRequestHeadersReceived) {
1069 			// If Transfer-Encoding is chunked, we should read a complete
1070 			// chunk in buffer before handling it
1071 			if (readByChunks) {
1072 				if (chunkSize >= 0) {
1073 					if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
1074 							// 2 more bytes to handle the closing CR+LF
1075 						bytesRead = chunkSize;
1076 						if (inputTempSize < chunkSize + 2) {
1077 							inputTempSize = chunkSize + 2;
1078 							inputTempBuffer
1079 								= new(std::nothrow) char[inputTempSize];
1080 							inputTempBufferDeleter.SetTo(inputTempBuffer);
1081 						}
1082 
1083 						if (inputTempBuffer == NULL) {
1084 							readError = B_NO_MEMORY;
1085 							break;
1086 						}
1087 
1088 						fInputBuffer.RemoveData(inputTempBuffer,
1089 							chunkSize + 2);
1090 						chunkSize = -1;
1091 					} else {
1092 						// Not enough data, try again later
1093 						bytesRead = -1;
1094 					}
1095 				} else {
1096 					BString chunkHeader;
1097 					if (_GetLine(chunkHeader) == B_ERROR) {
1098 						chunkSize = -1;
1099 						bytesRead = -1;
1100 					} else {
1101 						// Format of a chunk header:
1102 						// <chunk size in hex>[; optional data]
1103 						int32 semiColonIndex = chunkHeader.FindFirst(';', 0);
1104 
1105 						// Cut-off optional data if present
1106 						if (semiColonIndex != -1) {
1107 							chunkHeader.Remove(semiColonIndex,
1108 								chunkHeader.Length() - semiColonIndex);
1109 						}
1110 
1111 						chunkSize = strtol(chunkHeader.String(), NULL, 16);
1112 						if (chunkSize == 0)
1113 							fRequestStatus = kRequestContentReceived;
1114 
1115 						bytesRead = -1;
1116 					}
1117 				}
1118 
1119 				// A chunk of 0 bytes indicates the end of the chunked transfer
1120 				if (bytesRead == 0)
1121 					receiveEnd = true;
1122 			} else {
1123 				bytesRead = fInputBuffer.Size();
1124 
1125 				if (bytesRead > 0) {
1126 					if (inputTempSize < bytesRead) {
1127 						inputTempSize = bytesRead;
1128 						inputTempBuffer = new(std::nothrow) char[bytesRead];
1129 						inputTempBufferDeleter.SetTo(inputTempBuffer);
1130 					}
1131 
1132 					if (inputTempBuffer == NULL) {
1133 						readError = B_NO_MEMORY;
1134 						break;
1135 					}
1136 					fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
1137 				}
1138 			}
1139 
1140 			if (bytesRead >= 0) {
1141 				bytesReceived += bytesRead;
1142 
1143 				if (fOutput != NULL && !disableListener) {
1144 					if (decompress) {
1145 						readError = decompressingStream->WriteExactly(
1146 							inputTempBuffer, bytesRead);
1147 						if (readError != B_OK)
1148 							break;
1149 
1150 						ssize_t size = decompressorStorage.Size();
1151 						BStackOrHeapArray<char, 4096> buffer(size);
1152 						size = decompressorStorage.Read(buffer, size);
1153 						if (size > 0) {
1154 							size_t written = 0;
1155 							readError = fOutput->WriteExactly(buffer,
1156 								size, &written);
1157 							if (fListener != NULL && written > 0)
1158 								fListener->BytesWritten(this, written);
1159 							if (readError != B_OK)
1160 								break;
1161 							bytesUnpacked += size;
1162 						}
1163 					} else if (bytesRead > 0) {
1164 						size_t written = 0;
1165 						readError = fOutput->WriteExactly(inputTempBuffer,
1166 							bytesRead, &written);
1167 						if (fListener != NULL && written > 0)
1168 							fListener->BytesWritten(this, written);
1169 						if (readError != B_OK)
1170 							break;
1171 					}
1172 				}
1173 
1174 				if (fListener != NULL && !disableListener)
1175 					fListener->DownloadProgress(this, bytesReceived,
1176 						std::max((off_t)0, bytesTotal));
1177 
1178 				if (bytesTotal >= 0 && bytesReceived >= bytesTotal)
1179 					receiveEnd = true;
1180 
1181 				if (decompress && receiveEnd && !disableListener) {
1182 					readError = decompressingStream->Flush();
1183 
1184 					if (readError == B_BUFFER_OVERFLOW)
1185 						readError = B_OK;
1186 
1187 					if (readError != B_OK)
1188 						break;
1189 
1190 					ssize_t size = decompressorStorage.Size();
1191 					BStackOrHeapArray<char, 4096> buffer(size);
1192 					size = decompressorStorage.Read(buffer, size);
1193 					if (fOutput != NULL && size > 0 && !disableListener) {
1194 						size_t written = 0;
1195 						readError = fOutput->WriteExactly(buffer, size,
1196 							&written);
1197 						if (fListener != NULL && written > 0)
1198 							fListener->BytesWritten(this, written);
1199 						if (readError != B_OK)
1200 							break;
1201 						bytesUnpacked += size;
1202 					}
1203 				}
1204 			}
1205 		}
1206 
1207 		parseEnd = (fInputBuffer.Size() == 0);
1208 	}
1209 
1210 	fSocket->Disconnect();
1211 
1212 	if (readError != B_OK)
1213 		return readError;
1214 
1215 	return fQuit ? B_INTERRUPTED : B_OK;
1216 }
1217 #endif // LIBNETAPI_DEPRECATED
1218 
1219 
1220 void
1221 BHttpRequest::_ParseStatus()
1222 {
1223 	// Status line should be formatted like: HTTP/M.m SSS ...
1224 	// With:   M = Major version of the protocol
1225 	//         m = Minor version of the protocol
1226 	//       SSS = three-digit status code of the response
1227 	//       ... = additional text info
1228 	BString statusLine;
1229 	if (_GetLine(statusLine) == B_ERROR)
1230 		return;
1231 
1232 	if (statusLine.CountChars() < 12)
1233 		return;
1234 
1235 	fRequestStatus = kRequestStatusReceived;
1236 
1237 	BString statusCodeStr;
1238 	BString statusText;
1239 	statusLine.CopyInto(statusCodeStr, 9, 3);
1240 	_SetResultStatusCode(atoi(statusCodeStr.String()));
1241 
1242 	statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13);
1243 
1244 	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)",
1245 		atoi(statusCodeStr.String()), _ResultStatusText().String());
1246 }
1247 
1248 
1249 void
1250 BHttpRequest::_ParseHeaders()
1251 {
1252 	BString currentHeader;
1253 	while (_GetLine(currentHeader) != B_ERROR) {
1254 		// An empty line means the end of the header section
1255 		if (currentHeader.Length() == 0) {
1256 			fRequestStatus = kRequestHeadersReceived;
1257 			return;
1258 		}
1259 
1260 		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s",
1261 			currentHeader.String());
1262 		fHeaders.AddHeader(currentHeader.String());
1263 	}
1264 }
1265 
1266 
1267 BString
1268 BHttpRequest::_SerializeRequest()
1269 {
1270 	BString request(fRequestMethod);
1271 	request << ' ';
1272 
1273 	if (fContext->UseProxy()) {
1274 		// When there is a proxy, the request must include the host and port so
1275 		// the proxy knows where to send the request.
1276 		request << Url().Protocol() << "://" << Url().Host();
1277 		if (Url().HasPort())
1278 			request << ':' << Url().Port();
1279 	}
1280 
1281 	if (Url().HasPath() && Url().Path().Length() > 0)
1282 		request << Url().Path();
1283 	else
1284 		request << '/';
1285 
1286 	if (Url().HasRequest())
1287 		request << '?' << Url().Request();
1288 
1289 	switch (fHttpVersion) {
1290 		case B_HTTP_11:
1291 			request << " HTTP/1.1\r\n";
1292 			break;
1293 
1294 		default:
1295 		case B_HTTP_10:
1296 			request << " HTTP/1.0\r\n";
1297 			break;
1298 	}
1299 
1300 	_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", request.String());
1301 
1302 	return request;
1303 }
1304 
1305 
1306 BString
1307 BHttpRequest::_SerializeHeaders()
1308 {
1309 	BHttpHeaders outputHeaders;
1310 
1311 	// HTTP 1.1 additional headers
1312 	if (fHttpVersion == B_HTTP_11) {
1313 		BString host = Url().Host();
1314 		if (Url().HasPort() && !_IsDefaultPort())
1315 			host << ':' << Url().Port();
1316 
1317 		outputHeaders.AddHeader("Host", host);
1318 
1319 		outputHeaders.AddHeader("Accept", "*/*");
1320 		outputHeaders.AddHeader("Accept-Encoding", "gzip");
1321 			// Allows the server to compress data using the "gzip" format.
1322 			// "deflate" is not supported, because there are two interpretations
1323 			// of what it means (the RFC and Microsoft products), and we don't
1324 			// want to handle this. Very few websites support only deflate,
1325 			// and most of them will send gzip, or at worst, uncompressed data.
1326 
1327 		outputHeaders.AddHeader("Connection", "close");
1328 			// Let the remote server close the connection after response since
1329 			// we don't handle multiple request on a single connection
1330 	}
1331 
1332 	// Classic HTTP headers
1333 	if (fOptUserAgent.CountChars() > 0)
1334 		outputHeaders.AddHeader("User-Agent", fOptUserAgent.String());
1335 
1336 	if (fOptReferer.CountChars() > 0)
1337 		outputHeaders.AddHeader("Referer", fOptReferer.String());
1338 
1339 	// Optional range requests headers
1340 	if (fOptRangeStart != -1 || fOptRangeEnd != -1) {
1341 		if (fOptRangeStart == -1)
1342 			fOptRangeStart = 0;
1343 		BString range;
1344 		if (fOptRangeEnd != -1) {
1345 			range.SetToFormat("bytes=%" B_PRIdOFF "-%" B_PRIdOFF,
1346 				fOptRangeStart, fOptRangeEnd);
1347 		} else {
1348 			range.SetToFormat("bytes=%" B_PRIdOFF "-", fOptRangeStart);
1349 		}
1350 		outputHeaders.AddHeader("Range", range.String());
1351 	}
1352 
1353 	// Authentication
1354 	if (fContext != NULL) {
1355 		BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl);
1356 		if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
1357 			if (fOptUsername.Length() > 0) {
1358 				authentication.SetUserName(fOptUsername);
1359 				authentication.SetPassword(fOptPassword);
1360 			}
1361 
1362 			BString request(fRequestMethod);
1363 			outputHeaders.AddHeader("Authorization",
1364 				authentication.Authorization(fUrl, request));
1365 		}
1366 	}
1367 
1368 	// Required headers for POST data
1369 	if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
1370 		BString contentType;
1371 
1372 		switch (fOptPostFields->GetFormType()) {
1373 			case B_HTTP_FORM_MULTIPART:
1374 				contentType << "multipart/form-data; boundary="
1375 					<< fOptPostFields->GetMultipartBoundary() << "";
1376 				break;
1377 
1378 			case B_HTTP_FORM_URL_ENCODED:
1379 				contentType << "application/x-www-form-urlencoded";
1380 				break;
1381 		}
1382 
1383 		outputHeaders.AddHeader("Content-Type", contentType);
1384 		outputHeaders.AddHeader("Content-Length",
1385 			fOptPostFields->ContentLength());
1386 	} else if (fOptInputData != NULL
1387 			&& (fRequestMethod == B_HTTP_POST
1388 			|| fRequestMethod == B_HTTP_PUT)) {
1389 		if (fOptInputDataSize >= 0)
1390 			outputHeaders.AddHeader("Content-Length", fOptInputDataSize);
1391 		else
1392 			outputHeaders.AddHeader("Transfer-Encoding", "chunked");
1393 	}
1394 
1395 	// Optional headers specified by the user
1396 	if (fOptHeaders != NULL) {
1397 		for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
1398 				headerIndex++) {
1399 			BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
1400 			int32 replaceIndex = outputHeaders.HasHeader(optHeader.Name());
1401 
1402 			// Add or replace the current option header to the
1403 			// output header list
1404 			if (replaceIndex == -1)
1405 				outputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
1406 			else
1407 				outputHeaders[replaceIndex].SetValue(optHeader.Value());
1408 		}
1409 	}
1410 
1411 	// Context cookies
1412 	if (fOptSetCookies && fContext != NULL) {
1413 		BString cookieString;
1414 
1415 		BNetworkCookieJar::UrlIterator iterator
1416 			= fContext->GetCookieJar().GetUrlIterator(fUrl);
1417 		const BNetworkCookie* cookie = iterator.Next();
1418 		if (cookie != NULL) {
1419 			while (true) {
1420 				cookieString << cookie->RawCookie(false);
1421 				cookie = iterator.Next();
1422 				if (cookie == NULL)
1423 					break;
1424 				cookieString << "; ";
1425 			}
1426 
1427 			outputHeaders.AddHeader("Cookie", cookieString);
1428 		}
1429 	}
1430 
1431 	// Write output headers to output stream
1432 	BString headerData;
1433 
1434 	for (int32 headerIndex = 0; headerIndex < outputHeaders.CountHeaders();
1435 			headerIndex++) {
1436 		const char* header = outputHeaders.HeaderAt(headerIndex).Header();
1437 
1438 		headerData << header;
1439 		headerData << "\r\n";
1440 
1441 		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", header);
1442 	}
1443 
1444 	return headerData;
1445 }
1446 
1447 
1448 void
1449 BHttpRequest::_SendPostData()
1450 {
1451 	if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) {
1452 		if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) {
1453 			BString outputBuffer = fOptPostFields->RawData();
1454 			_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
1455 				"%s", outputBuffer.String());
1456 			fSocket->Write(outputBuffer.String(), outputBuffer.Length());
1457 		} else {
1458 			for (BHttpForm::Iterator it = fOptPostFields->GetIterator();
1459 				const BHttpFormData* currentField = it.Next();
1460 				) {
1461 				_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
1462 					it.MultipartHeader().String());
1463 				fSocket->Write(it.MultipartHeader().String(),
1464 					it.MultipartHeader().Length());
1465 
1466 				switch (currentField->Type()) {
1467 					default:
1468 					case B_HTTPFORM_UNKNOWN:
1469 						ASSERT(0);
1470 						break;
1471 
1472 					case B_HTTPFORM_STRING:
1473 						fSocket->Write(currentField->String().String(),
1474 							currentField->String().Length());
1475 						break;
1476 
1477 					case B_HTTPFORM_FILE:
1478 						{
1479 							BFile upFile(currentField->File().Path(),
1480 								B_READ_ONLY);
1481 							char readBuffer[kHttpBufferSize];
1482 							ssize_t readSize;
1483 							off_t totalSize;
1484 
1485 							if (upFile.GetSize(&totalSize) != B_OK)
1486 								ASSERT(0);
1487 
1488 							readSize = upFile.Read(readBuffer,
1489 								sizeof(readBuffer));
1490 							while (readSize > 0) {
1491 								fSocket->Write(readBuffer, readSize);
1492 								readSize = upFile.Read(readBuffer,
1493 									sizeof(readBuffer));
1494 								fListener->UploadProgress(this, readSize,
1495 									std::max((off_t)0, totalSize));
1496 							}
1497 
1498 							break;
1499 						}
1500 					case B_HTTPFORM_BUFFER:
1501 						fSocket->Write(currentField->Buffer(),
1502 							currentField->BufferSize());
1503 						break;
1504 				}
1505 
1506 				fSocket->Write("\r\n", 2);
1507 			}
1508 
1509 			BString footer = fOptPostFields->GetMultipartFooter();
1510 			fSocket->Write(footer.String(), footer.Length());
1511 		}
1512 	} else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)
1513 		&& fOptInputData != NULL) {
1514 
1515 		// If the input data is seekable, we rewind it for each new request.
1516 		BPositionIO* seekableData
1517 			= dynamic_cast<BPositionIO*>(fOptInputData);
1518 		if (seekableData)
1519 			seekableData->Seek(0, SEEK_SET);
1520 
1521 		for (;;) {
1522 			char outputTempBuffer[kHttpBufferSize];
1523 			ssize_t read = fOptInputData->Read(outputTempBuffer,
1524 				sizeof(outputTempBuffer));
1525 
1526 			if (read <= 0)
1527 				break;
1528 
1529 			if (fOptInputDataSize < 0) {
1530 				// Input data size unknown, so we have to use chunked transfer
1531 				char hexSize[18];
1532 				// The string does not need to be NULL terminated.
1533 				size_t hexLength = snprintf(hexSize, sizeof(hexSize), "%lx\r\n",
1534 					read);
1535 
1536 				fSocket->Write(hexSize, hexLength);
1537 				fSocket->Write(outputTempBuffer, read);
1538 				fSocket->Write("\r\n", 2);
1539 			} else {
1540 				fSocket->Write(outputTempBuffer, read);
1541 			}
1542 		}
1543 
1544 		if (fOptInputDataSize < 0) {
1545 			// Chunked transfer terminating sequence
1546 			fSocket->Write("0\r\n\r\n", 5);
1547 		}
1548 	}
1549 
1550 }
1551 
1552 
1553 BHttpHeaders&
1554 BHttpRequest::_ResultHeaders()
1555 {
1556 	return fResult.fHeaders;
1557 }
1558 
1559 
1560 void
1561 BHttpRequest::_SetResultStatusCode(int32 statusCode)
1562 {
1563 	fResult.fStatusCode = statusCode;
1564 }
1565 
1566 
1567 BString&
1568 BHttpRequest::_ResultStatusText()
1569 {
1570 	return fResult.fStatusString;
1571 }
1572 
1573 
1574 bool
1575 BHttpRequest::_CertificateVerificationFailed(BCertificate& certificate,
1576 	const char* message)
1577 {
1578 	if (fContext->HasCertificateException(certificate))
1579 		return true;
1580 
1581 	if (fListener != NULL
1582 		&& fListener->CertificateVerificationFailed(this, certificate, message)) {
1583 		// User asked us to continue anyway, let's add a temporary exception for this certificate
1584 		fContext->AddCertificateException(certificate);
1585 		return true;
1586 	}
1587 
1588 	return false;
1589 }
1590 
1591 
1592 bool
1593 BHttpRequest::_IsDefaultPort()
1594 {
1595 	if (fSSL && Url().Port() == 443)
1596 		return true;
1597 	if (!fSSL && Url().Port() == 80)
1598 		return true;
1599 	return false;
1600 }
1601 
1602 
1603 #ifdef LIBNETAPI_DEPRECATED
1604 void
1605 BHttpRequest::_NotifyDataReceived(const char* data, off_t pos, ssize_t size,
1606 	off_t bytesReceived, ssize_t bytesTotal)
1607 {
1608 	if (fListener == NULL || size <= 0)
1609 		return;
1610 	if (fOptRangeStart > 0) {
1611 		pos += fOptRangeStart;
1612 		// bytesReceived and bytesTotal refer to the requested range,
1613 		// so that should technically not be adjusted for the range start.
1614 		// For displaying progress to the user, this is not ideal, though.
1615 		// But only for the case where we request the remainder of a file.
1616 		// Range requests can also be used to request any portion of a
1617 		// resource, so not modifying them is technically more correct.
1618 		// We can use a little trick, though: We know when the remainder
1619 		// is requested, because then fOptRangeEnd is -1.
1620 		if (fOptRangeEnd == -1) {
1621 			bytesReceived += fOptRangeStart;
1622 			if (bytesTotal > 0)
1623 				bytesTotal += fOptRangeStart;
1624 		}
1625 	}
1626 	fListener->DataReceived(this, data, pos, size);
1627 	fListener->DownloadProgress(this, bytesReceived,
1628 		std::max((ssize_t)0, bytesTotal));
1629 }
1630 #endif
1631