⛏️ index : haiku.git

/*
 * Copyright 2010-2021 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Christophe Huriaux, c.huriaux@gmail.com
 *		Niels Sascha Reedijk, niels.reedijk@gmail.com
 *		Adrien Destugues, pulkomandy@pulkomandy.tk
 *		Stephan Aßmus, superstippi@gmx.de
 */


#include <HttpRequest.h>

#include <arpa/inet.h>
#include <stdio.h>

#include <cstdlib>
#include <deque>
#include <new>

#include <AutoDeleter.h>
#include <Certificate.h>
#include <Debug.h>
#include <DynamicBuffer.h>
#include <File.h>
#include <ProxySecureSocket.h>
#include <Socket.h>
#include <SecureSocket.h>
#include <StackOrHeapArray.h>
#include <ZlibCompressionAlgorithm.h>

using namespace BPrivate::Network;


static const int32 kHttpBufferSize = 4096;


namespace BPrivate {

	class CheckedSecureSocket: public BSecureSocket
	{
		public:
			CheckedSecureSocket(BHttpRequest* request);

			bool			CertificateVerificationFailed(BCertificate& certificate,
					const char* message);

		private:
			BHttpRequest*	fRequest;
	};


	CheckedSecureSocket::CheckedSecureSocket(BHttpRequest* request)
		:
		BSecureSocket(),
		fRequest(request)
	{
	}


	bool
	CheckedSecureSocket::CertificateVerificationFailed(BCertificate& certificate,
		const char* message)
	{
		return fRequest->_CertificateVerificationFailed(certificate, message);
	}


	class CheckedProxySecureSocket: public BProxySecureSocket
	{
		public:
			CheckedProxySecureSocket(const BNetworkAddress& proxy, BHttpRequest* request);

			bool			CertificateVerificationFailed(BCertificate& certificate,
					const char* message);

		private:
			BHttpRequest*	fRequest;
	};


	CheckedProxySecureSocket::CheckedProxySecureSocket(const BNetworkAddress& proxy,
		BHttpRequest* request)
		:
		BProxySecureSocket(proxy),
		fRequest(request)
	{
	}


	bool
	CheckedProxySecureSocket::CertificateVerificationFailed(BCertificate& certificate,
		const char* message)
	{
		return fRequest->_CertificateVerificationFailed(certificate, message);
	}
};


BHttpRequest::BHttpRequest(const BUrl& url, BDataIO* output, bool ssl,
	const char* protocolName, BUrlProtocolListener* listener,
	BUrlContext* context)
	:
	BNetworkRequest(url, output, listener, context, "BUrlProtocol.HTTP",
		protocolName),
	fSSL(ssl),
	fRequestMethod(B_HTTP_GET),
	fHttpVersion(B_HTTP_11),
	fResult(url),
	fRequestStatus(kRequestInitialState),
	fOptHeaders(NULL),
	fOptPostFields(NULL),
	fOptInputData(NULL),
	fOptInputDataSize(-1),
	fOptRangeStart(-1),
	fOptRangeEnd(-1),
	fOptFollowLocation(true)
{
	_ResetOptions();
	fSocket = NULL;
}


BHttpRequest::BHttpRequest(const BHttpRequest& other)
	:
	BNetworkRequest(other.Url(), other.Output(), other.fListener,
		other.fContext, "BUrlProtocol.HTTP", other.fSSL ? "HTTPS" : "HTTP"),
	fSSL(other.fSSL),
	fRequestMethod(other.fRequestMethod),
	fHttpVersion(other.fHttpVersion),
	fResult(other.fUrl),
	fRequestStatus(kRequestInitialState),
	fOptHeaders(NULL),
	fOptPostFields(NULL),
	fOptInputData(NULL),
	fOptInputDataSize(-1),
	fOptRangeStart(other.fOptRangeStart),
	fOptRangeEnd(other.fOptRangeEnd),
	fOptFollowLocation(other.fOptFollowLocation)
{
	_ResetOptions();
		// FIXME some options may be copied from other instead.
	fSocket = NULL;
}


BHttpRequest::~BHttpRequest()
{
	Stop();

	delete fSocket;

	delete fOptInputData;
	delete fOptHeaders;
	delete fOptPostFields;
}


void
BHttpRequest::SetMethod(const char* const method)
{
	fRequestMethod = method;
}


void
BHttpRequest::SetFollowLocation(bool follow)
{
	fOptFollowLocation = follow;
}


void
BHttpRequest::SetMaxRedirections(int8 redirections)
{
	fOptMaxRedirs = redirections;
}


void
BHttpRequest::SetReferrer(const BString& referrer)
{
	fOptReferer = referrer;
}


void
BHttpRequest::SetUserAgent(const BString& agent)
{
	fOptUserAgent = agent;
}


void
BHttpRequest::SetDiscardData(bool discard)
{
	fOptDiscardData = discard;
}


void
BHttpRequest::SetDisableListener(bool disable)
{
	fOptDisableListener = disable;
}


void
BHttpRequest::SetAutoReferrer(bool enable)
{
	fOptAutoReferer = enable;
}


void
BHttpRequest::SetStopOnError(bool stop)
{
	fOptStopOnError = stop;
}


void
BHttpRequest::SetUserName(const BString& name)
{
	fOptUsername = name;
}


void
BHttpRequest::SetPassword(const BString& password)
{
	fOptPassword = password;
}


void
BHttpRequest::SetRangeStart(off_t position)
{
	// This field is used within the transfer loop, so only
	// allow setting it before sending the request.
	if (fRequestStatus == kRequestInitialState)
		fOptRangeStart = position;
}


void
BHttpRequest::SetRangeEnd(off_t position)
{
	// This field could be used in the transfer loop, so only
	// allow setting it before sending the request.
	if (fRequestStatus == kRequestInitialState)
		fOptRangeEnd = position;
}


void
BHttpRequest::SetPostFields(const BHttpForm& fields)
{
	AdoptPostFields(new(std::nothrow) BHttpForm(fields));
}


void
BHttpRequest::SetHeaders(const BHttpHeaders& headers)
{
	AdoptHeaders(new(std::nothrow) BHttpHeaders(headers));
}


void
BHttpRequest::AdoptPostFields(BHttpForm* const fields)
{
	delete fOptPostFields;
	fOptPostFields = fields;

	if (fOptPostFields != NULL)
		fRequestMethod = B_HTTP_POST;
}


void
BHttpRequest::AdoptInputData(BDataIO* const data, const ssize_t size)
{
	delete fOptInputData;
	fOptInputData = data;
	fOptInputDataSize = size;
}


void
BHttpRequest::AdoptHeaders(BHttpHeaders* const headers)
{
	delete fOptHeaders;
	fOptHeaders = headers;
}


/*static*/ bool
BHttpRequest::IsInformationalStatusCode(int16 code)
{
	return (code >= B_HTTP_STATUS__INFORMATIONAL_BASE)
		&& (code <  B_HTTP_STATUS__INFORMATIONAL_END);
}


/*static*/ bool
BHttpRequest::IsSuccessStatusCode(int16 code)
{
	return (code >= B_HTTP_STATUS__SUCCESS_BASE)
		&& (code <  B_HTTP_STATUS__SUCCESS_END);
}


/*static*/ bool
BHttpRequest::IsRedirectionStatusCode(int16 code)
{
	return (code >= B_HTTP_STATUS__REDIRECTION_BASE)
		&& (code <  B_HTTP_STATUS__REDIRECTION_END);
}


/*static*/ bool
BHttpRequest::IsClientErrorStatusCode(int16 code)
{
	return (code >= B_HTTP_STATUS__CLIENT_ERROR_BASE)
		&& (code <  B_HTTP_STATUS__CLIENT_ERROR_END);
}


/*static*/ bool
BHttpRequest::IsServerErrorStatusCode(int16 code)
{
	return (code >= B_HTTP_STATUS__SERVER_ERROR_BASE)
		&& (code <  B_HTTP_STATUS__SERVER_ERROR_END);
}


/*static*/ int16
BHttpRequest::StatusCodeClass(int16 code)
{
	if (BHttpRequest::IsInformationalStatusCode(code))
		return B_HTTP_STATUS_CLASS_INFORMATIONAL;
	else if (BHttpRequest::IsSuccessStatusCode(code))
		return B_HTTP_STATUS_CLASS_SUCCESS;
	else if (BHttpRequest::IsRedirectionStatusCode(code))
		return B_HTTP_STATUS_CLASS_REDIRECTION;
	else if (BHttpRequest::IsClientErrorStatusCode(code))
		return B_HTTP_STATUS_CLASS_CLIENT_ERROR;
	else if (BHttpRequest::IsServerErrorStatusCode(code))
		return B_HTTP_STATUS_CLASS_SERVER_ERROR;

	return B_HTTP_STATUS_CLASS_INVALID;
}


const BUrlResult&
BHttpRequest::Result() const
{
	return fResult;
}


status_t
BHttpRequest::Stop()
{

	if (fSocket != NULL) {
		fSocket->Disconnect();
			// Unlock any pending connect, read or write operation.
	}
	return BNetworkRequest::Stop();
}


void
BHttpRequest::_ResetOptions()
{
	delete fOptPostFields;
	delete fOptHeaders;

	fOptFollowLocation = true;
	fOptMaxRedirs = 8;
	fOptReferer = "";
	fOptUserAgent = "Services Kit (Haiku)";
	fOptUsername = "";
	fOptPassword = "";
	fOptAuthMethods = B_HTTP_AUTHENTICATION_BASIC | B_HTTP_AUTHENTICATION_DIGEST
		| B_HTTP_AUTHENTICATION_IE_DIGEST;
	fOptHeaders = NULL;
	fOptPostFields = NULL;
	fOptSetCookies = true;
	fOptDiscardData = false;
	fOptDisableListener = false;
	fOptAutoReferer = true;
}


status_t
BHttpRequest::_ProtocolLoop()
{
	// Initialize the request redirection loop
	int8 maxRedirs = fOptMaxRedirs;
	bool newRequest;

	do {
		newRequest = false;

		// Result reset
		fHeaders.Clear();
		_ResultHeaders().Clear();

		BString host = fUrl.Host();
		int port = fSSL ? 443 : 80;

		if (fUrl.HasPort())
			port = fUrl.Port();

		if (fContext->UseProxy()) {
			host = fContext->GetProxyHost();
			port = fContext->GetProxyPort();
		}

		status_t result = fInputBuffer.InitCheck();
		if (result != B_OK)
			return result;

		if (!_ResolveHostName(host, port)) {
			_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR,
				"Unable to resolve hostname (%s), aborting.",
					fUrl.Host().String());
			return B_SERVER_NOT_FOUND;
		}

		status_t requestStatus = _MakeRequest();
		if (requestStatus != B_OK)
			return requestStatus;

		// Prepare the referer for the next request if needed
		if (fOptAutoReferer)
			fOptReferer = fUrl.UrlString();

		switch (StatusCodeClass(fResult.StatusCode())) {
			case B_HTTP_STATUS_CLASS_INFORMATIONAL:
				// Header 100:continue should have been
				// handled in the _MakeRequest read loop
				break;

			case B_HTTP_STATUS_CLASS_SUCCESS:
				break;

			case B_HTTP_STATUS_CLASS_REDIRECTION:
			{
				// Redirection has been explicitly disabled
				if (!fOptFollowLocation)
					break;

				int code = fResult.StatusCode();
				if (code == B_HTTP_STATUS_MOVED_PERMANENTLY
					|| code == B_HTTP_STATUS_FOUND
					|| code == B_HTTP_STATUS_SEE_OTHER
					|| code == B_HTTP_STATUS_TEMPORARY_REDIRECT) {
					BString locationUrl = fHeaders["Location"];

					fUrl = BUrl(fUrl, locationUrl);

					// 302 and 303 redirections also convert POST requests to GET
					// (and remove the posted form data)
					if ((code == B_HTTP_STATUS_FOUND
						|| code == B_HTTP_STATUS_SEE_OTHER)
						&& fRequestMethod == B_HTTP_POST) {
						SetMethod(B_HTTP_GET);
						delete fOptPostFields;
						fOptPostFields = NULL;
						delete fOptInputData;
						fOptInputData = NULL;
						fOptInputDataSize = 0;
					}

					if (--maxRedirs > 0) {
						newRequest = true;

						// Redirections may need a switch from http to https.
						if (fUrl.Protocol() == "https")
							fSSL = true;
						else if (fUrl.Protocol() == "http")
							fSSL = false;

						_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
							"Following: %s\n",
							fUrl.UrlString().String());
					}
				}
				break;
			}

			case B_HTTP_STATUS_CLASS_CLIENT_ERROR:
				if (fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) {
					BHttpAuthentication* authentication
						= &fContext->GetAuthentication(fUrl);
					status_t status = B_OK;

					if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) {
						// There is no authentication context for this
						// url yet, so let's create one.
						BHttpAuthentication newAuth;
						newAuth.Initialize(fHeaders["WWW-Authenticate"]);
						fContext->AddAuthentication(fUrl, newAuth);

						// Get the copy of the authentication we just added.
						// That copy is owned by the BUrlContext and won't be
						// deleted (unlike the temporary object above)
						authentication = &fContext->GetAuthentication(fUrl);
					}

					newRequest = false;
					if (fOptUsername.Length() > 0 && status == B_OK) {
						// If we received an username and password, add them
						// to the request. This will either change the
						// credentials for an existing request, or set them
						// for a new one we created just above.
						//
						// If this request handles HTTP redirections, it will
						// also automatically retry connecting and send the
						// login information.
						authentication->SetUserName(fOptUsername);
						authentication->SetPassword(fOptPassword);
						newRequest = true;
					}
				}
				break;

			case B_HTTP_STATUS_CLASS_SERVER_ERROR:
				break;

			default:
			case B_HTTP_STATUS_CLASS_INVALID:
				break;
		}
	} while (newRequest);

	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
		"%" B_PRId32 " headers and %" B_PRIuSIZE " bytes of data remaining",
		fHeaders.CountHeaders(), fInputBuffer.Size());

	if (fResult.StatusCode() == 404)
		return B_RESOURCE_NOT_FOUND;

	return B_OK;
}


status_t
BHttpRequest::_MakeRequest()
{
	delete fSocket;

	if (fSSL) {
		if (fContext->UseProxy()) {
			BNetworkAddress proxy(fContext->GetProxyHost(), fContext->GetProxyPort());
			fSocket = new(std::nothrow) BPrivate::CheckedProxySecureSocket(proxy, this);
		} else
			fSocket = new(std::nothrow) BPrivate::CheckedSecureSocket(this);
	} else
		fSocket = new(std::nothrow) BSocket();

	if (fSocket == NULL)
		return B_NO_MEMORY;

	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.",
		fUrl.Authority().String(), fRemoteAddr.Port());
	status_t connectError = fSocket->Connect(fRemoteAddr);

	if (connectError != B_OK) {
		_EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s",
			strerror(connectError));
		return connectError;
	}

	//! ProtocolHook:ConnectionOpened
	if (fListener != NULL)
		fListener->ConnectionOpened(this);

	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
		"Connection opened, sending request.");

	BString requestHeaders;
	requestHeaders.Append(_SerializeRequest());
	requestHeaders.Append(_SerializeHeaders());
	requestHeaders.Append("\r\n");
	fSocket->Write(requestHeaders.String(), requestHeaders.Length());
	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent.");

	_SendPostData();
	fRequestStatus = kRequestInitialState;



	// Receive loop
	bool disableListener = false;
	bool receiveEnd = false;
	bool parseEnd = false;
	bool readByChunks = false;
	bool decompress = false;
	status_t readError = B_OK;
	ssize_t bytesRead = 0;
	off_t bytesReceived = 0;
	off_t bytesTotal = 0;
	size_t previousBufferSize = 0;
	off_t bytesUnpacked = 0;
	char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize];
	ArrayDeleter<char> inputTempBufferDeleter(inputTempBuffer);
	ssize_t inputTempSize = kHttpBufferSize;
	ssize_t chunkSize = -1;
	DynamicBuffer decompressorStorage;
	BDataIO* decompressingStream;
	ObjectDeleter<BDataIO> decompressingStreamDeleter;

	while (!fQuit && !(receiveEnd && parseEnd)) {
		if ((!receiveEnd) && (fInputBuffer.Size() == previousBufferSize)) {
			BStackOrHeapArray<char, 4096> chunk(kHttpBufferSize);
			bytesRead = fSocket->Read(chunk, kHttpBufferSize);

			if (bytesRead < 0) {
				readError = bytesRead;
				break;
			} else if (bytesRead == 0) {
				// Check if we got the expected number of bytes.
				// Exceptions:
				// - If the content-length is not known (bytesTotal is 0), for
				//   example in the case of a chunked transfer, we can't know
				// - If the request method is "HEAD" which explicitly asks the
				//   server to not send any data (only the headers)
				if (bytesTotal > 0 && bytesReceived != bytesTotal) {
					readError = B_IO_ERROR;
					break;
				}
				receiveEnd = true;
			}

			fInputBuffer.AppendData(chunk, bytesRead);
		} else
			bytesRead = 0;

		previousBufferSize = fInputBuffer.Size();

		if (fRequestStatus < kRequestStatusReceived) {
			_ParseStatus();

			if (fOptFollowLocation
					&& IsRedirectionStatusCode(fResult.StatusCode()))
				disableListener = true;

			if (fOptStopOnError
					&& fResult.StatusCode() >= B_HTTP_STATUS_CLASS_CLIENT_ERROR)
			{
				fQuit = true;
				break;
			}

			//! ProtocolHook:ResponseStarted
			if (fRequestStatus >= kRequestStatusReceived && fListener != NULL
					&& !disableListener)
				fListener->ResponseStarted(this);
		}

		if (fRequestStatus < kRequestHeadersReceived) {
			_ParseHeaders();

			if (fRequestStatus >= kRequestHeadersReceived) {
				_ResultHeaders() = fHeaders;

				// Parse received cookies
				if (fContext != NULL) {
					for (int32 i = 0;  i < fHeaders.CountHeaders(); i++) {
						if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) {
							fContext->GetCookieJar().AddCookie(
								fHeaders.HeaderAt(i).Value(), fUrl);
						}
					}
				}

				//! ProtocolHook:HeadersReceived
				if (fListener != NULL && !disableListener)
					fListener->HeadersReceived(this);


				if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
					readByChunks = true;

				BString contentEncoding(fHeaders["Content-Encoding"]);
				// We don't advertise "deflate" support (see above), but we
				// still try to decompress it, if a server ever sends a deflate
				// stream despite it not being in our Accept-Encoding list.
				if (contentEncoding == "gzip"
						|| contentEncoding == "deflate") {
					decompress = true;
					readError = BZlibCompressionAlgorithm()
						.CreateDecompressingOutputStream(&decompressorStorage,
							NULL, decompressingStream);
					if (readError != B_OK)
						break;

					decompressingStreamDeleter.SetTo(decompressingStream);
				}

				int32 index = fHeaders.HasHeader("Content-Length");
				if (index != B_ERROR)
					bytesTotal = atoll(fHeaders.HeaderAt(index).Value());
				else
					bytesTotal = -1;

				if (fRequestMethod == B_HTTP_HEAD
					|| fResult.StatusCode() == 204) {
					// In the case of a HEAD request or if the server replies
					// 204 ("no content"), we don't expect to receive anything
					// more, and the socket will be closed.
					receiveEnd = true;
				}
			}
		}

		if (fRequestStatus >= kRequestHeadersReceived) {
			// If Transfer-Encoding is chunked, we should read a complete
			// chunk in buffer before handling it
			if (readByChunks) {
				if (chunkSize >= 0) {
					if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
							// 2 more bytes to handle the closing CR+LF
						bytesRead = chunkSize;
						if (inputTempSize < chunkSize + 2) {
							inputTempSize = chunkSize + 2;
							inputTempBuffer
								= new(std::nothrow) char[inputTempSize];
							inputTempBufferDeleter.SetTo(inputTempBuffer);
						}

						if (inputTempBuffer == NULL) {
							readError = B_NO_MEMORY;
							break;
						}

						fInputBuffer.RemoveData(inputTempBuffer,
							chunkSize + 2);
						chunkSize = -1;
					} else {
						// Not enough data, try again later
						bytesRead = -1;
					}
				} else {
					BString chunkHeader;
					if (_GetLine(chunkHeader) == B_ERROR) {
						chunkSize = -1;
						bytesRead = -1;
					} else {
						// Format of a chunk header:
						// <chunk size in hex>[; optional data]
						int32 semiColonIndex = chunkHeader.FindFirst(';', 0);

						// Cut-off optional data if present
						if (semiColonIndex != -1) {
							chunkHeader.Remove(semiColonIndex,
								chunkHeader.Length() - semiColonIndex);
						}

						chunkSize = strtol(chunkHeader.String(), NULL, 16);
						if (chunkSize == 0)
							fRequestStatus = kRequestContentReceived;

						bytesRead = -1;
					}
				}

				// A chunk of 0 bytes indicates the end of the chunked transfer
				if (bytesRead == 0)
					receiveEnd = true;
			} else {
				bytesRead = fInputBuffer.Size();

				if (bytesRead > 0) {
					if (inputTempSize < bytesRead) {
						inputTempSize = bytesRead;
						inputTempBuffer = new(std::nothrow) char[bytesRead];
						inputTempBufferDeleter.SetTo(inputTempBuffer);
					}

					if (inputTempBuffer == NULL) {
						readError = B_NO_MEMORY;
						break;
					}
					fInputBuffer.RemoveData(inputTempBuffer, bytesRead);
				}
			}

			if (bytesRead >= 0) {
				bytesReceived += bytesRead;

				if (fOutput != NULL && !disableListener) {
					if (decompress) {
						readError = decompressingStream->WriteExactly(
							inputTempBuffer, bytesRead);
						if (readError != B_OK)
							break;

						ssize_t size = decompressorStorage.Size();
						BStackOrHeapArray<char, 4096> buffer(size);
						size = decompressorStorage.Read(buffer, size);
						if (size > 0) {
							size_t written = 0;
							readError = fOutput->WriteExactly(buffer,
								size, &written);
							if (fListener != NULL && written > 0)
								fListener->BytesWritten(this, written);
							if (readError != B_OK)
								break;
							bytesUnpacked += size;
						}
					} else if (bytesRead > 0) {
						size_t written = 0;
						readError = fOutput->WriteExactly(inputTempBuffer,
							bytesRead, &written);
						if (fListener != NULL && written > 0)
							fListener->BytesWritten(this, written);
						if (readError != B_OK)
							break;
					}
				}

				if (fListener != NULL && !disableListener)
					fListener->DownloadProgress(this, bytesReceived,
						std::max((off_t)0, bytesTotal));

				if (bytesTotal >= 0 && bytesReceived >= bytesTotal)
					receiveEnd = true;

				if (decompress && receiveEnd && !disableListener) {
					readError = decompressingStream->Flush();

					if (readError == B_BUFFER_OVERFLOW)
						readError = B_OK;

					if (readError != B_OK)
						break;

					ssize_t size = decompressorStorage.Size();
					BStackOrHeapArray<char, 4096> buffer(size);
					size = decompressorStorage.Read(buffer, size);
					if (fOutput != NULL && size > 0 && !disableListener) {
						size_t written = 0;
						readError = fOutput->WriteExactly(buffer, size,
							&written);
						if (fListener != NULL && written > 0)
							fListener->BytesWritten(this, written);
						if (readError != B_OK)
							break;
						bytesUnpacked += size;
					}
				}
			}
		}

		parseEnd = (fInputBuffer.Size() == 0);
	}

	fSocket->Disconnect();

	if (readError != B_OK)
		return readError;

	return fQuit ? B_INTERRUPTED : B_OK;
}


void
BHttpRequest::_ParseStatus()
{
	// Status line should be formatted like: HTTP/M.m SSS ...
	// With:   M = Major version of the protocol
	//         m = Minor version of the protocol
	//       SSS = three-digit status code of the response
	//       ... = additional text info
	BString statusLine;
	if (_GetLine(statusLine) == B_ERROR)
		return;

	if (statusLine.CountChars() < 12)
		return;

	fRequestStatus = kRequestStatusReceived;

	BString statusCodeStr;
	BString statusText;
	statusLine.CopyInto(statusCodeStr, 9, 3);
	_SetResultStatusCode(atoi(statusCodeStr.String()));

	statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13);

	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)",
		atoi(statusCodeStr.String()), _ResultStatusText().String());
}


void
BHttpRequest::_ParseHeaders()
{
	BString currentHeader;
	while (_GetLine(currentHeader) != B_ERROR) {
		// An empty line means the end of the header section
		if (currentHeader.Length() == 0) {
			fRequestStatus = kRequestHeadersReceived;
			return;
		}

		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s",
			currentHeader.String());
		fHeaders.AddHeader(currentHeader.String());
	}
}


BString
BHttpRequest::_SerializeRequest()
{
	BString request(fRequestMethod);
	request << ' ';

	if (fContext->UseProxy()) {
		// When there is a proxy, the request must include the host and port so
		// the proxy knows where to send the request.
		request << Url().Protocol() << "://" << Url().Host();
		if (Url().HasPort())
			request << ':' << Url().Port();
	}

	if (Url().HasPath() && Url().Path().Length() > 0)
		request << Url().Path();
	else
		request << '/';

	if (Url().HasRequest())
		request << '?' << Url().Request();

	switch (fHttpVersion) {
		case B_HTTP_11:
			request << " HTTP/1.1\r\n";
			break;

		default:
		case B_HTTP_10:
			request << " HTTP/1.0\r\n";
			break;
	}

	_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", request.String());

	return request;
}


BString
BHttpRequest::_SerializeHeaders()
{
	BHttpHeaders outputHeaders;

	// HTTP 1.1 additional headers
	if (fHttpVersion == B_HTTP_11) {
		BString host = Url().Host();
		if (Url().HasPort() && !_IsDefaultPort())
			host << ':' << Url().Port();

		outputHeaders.AddHeader("Host", host);

		outputHeaders.AddHeader("Accept", "*/*");
		outputHeaders.AddHeader("Accept-Encoding", "gzip");
			// Allows the server to compress data using the "gzip" format.
			// "deflate" is not supported, because there are two interpretations
			// of what it means (the RFC and Microsoft products), and we don't
			// want to handle this. Very few websites support only deflate,
			// and most of them will send gzip, or at worst, uncompressed data.

		outputHeaders.AddHeader("Connection", "close");
			// Let the remote server close the connection after response since
			// we don't handle multiple request on a single connection
	}

	// Classic HTTP headers
	if (fOptUserAgent.CountChars() > 0)
		outputHeaders.AddHeader("User-Agent", fOptUserAgent.String());

	if (fOptReferer.CountChars() > 0)
		outputHeaders.AddHeader("Referer", fOptReferer.String());

	// Optional range requests headers
	if (fOptRangeStart != -1 || fOptRangeEnd != -1) {
		if (fOptRangeStart == -1)
			fOptRangeStart = 0;
		BString range;
		if (fOptRangeEnd != -1) {
			range.SetToFormat("bytes=%" B_PRIdOFF "-%" B_PRIdOFF,
				fOptRangeStart, fOptRangeEnd);
		} else {
			range.SetToFormat("bytes=%" B_PRIdOFF "-", fOptRangeStart);
		}
		outputHeaders.AddHeader("Range", range.String());
	}

	// Authentication
	if (fContext != NULL) {
		BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl);
		if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
			if (fOptUsername.Length() > 0) {
				authentication.SetUserName(fOptUsername);
				authentication.SetPassword(fOptPassword);
			}

			BString request(fRequestMethod);
			outputHeaders.AddHeader("Authorization",
				authentication.Authorization(fUrl, request));
		}
	}

	// Required headers for POST data
	if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
		BString contentType;

		switch (fOptPostFields->GetFormType()) {
			case B_HTTP_FORM_MULTIPART:
				contentType << "multipart/form-data; boundary="
					<< fOptPostFields->GetMultipartBoundary() << "";
				break;

			case B_HTTP_FORM_URL_ENCODED:
				contentType << "application/x-www-form-urlencoded";
				break;
		}

		outputHeaders.AddHeader("Content-Type", contentType);
		outputHeaders.AddHeader("Content-Length",
			fOptPostFields->ContentLength());
	} else if (fOptInputData != NULL
			&& (fRequestMethod == B_HTTP_POST
			|| fRequestMethod == B_HTTP_PUT)) {
		if (fOptInputDataSize >= 0)
			outputHeaders.AddHeader("Content-Length", fOptInputDataSize);
		else
			outputHeaders.AddHeader("Transfer-Encoding", "chunked");
	}

	// Optional headers specified by the user
	if (fOptHeaders != NULL) {
		for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
				headerIndex++) {
			BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
			int32 replaceIndex = outputHeaders.HasHeader(optHeader.Name());

			// Add or replace the current option header to the
			// output header list
			if (replaceIndex == -1)
				outputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
			else
				outputHeaders[replaceIndex].SetValue(optHeader.Value());
		}
	}

	// Context cookies
	if (fOptSetCookies && fContext != NULL) {
		BString cookieString;

		BNetworkCookieJar::UrlIterator iterator
			= fContext->GetCookieJar().GetUrlIterator(fUrl);
		const BNetworkCookie* cookie = iterator.Next();
		if (cookie != NULL) {
			while (true) {
				cookieString << cookie->RawCookie(false);
				cookie = iterator.Next();
				if (cookie == NULL)
					break;
				cookieString << "; ";
			}

			outputHeaders.AddHeader("Cookie", cookieString);
		}
	}

	// Write output headers to output stream
	BString headerData;

	for (int32 headerIndex = 0; headerIndex < outputHeaders.CountHeaders();
			headerIndex++) {
		const char* header = outputHeaders.HeaderAt(headerIndex).Header();

		headerData << header;
		headerData << "\r\n";

		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", header);
	}

	return headerData;
}


void
BHttpRequest::_SendPostData()
{
	if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) {
		if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) {
			BString outputBuffer = fOptPostFields->RawData();
			_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
				"%s", outputBuffer.String());
			fSocket->Write(outputBuffer.String(), outputBuffer.Length());
		} else {
			for (BHttpForm::Iterator it = fOptPostFields->GetIterator();
				const BHttpFormData* currentField = it.Next();
				) {
				_EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT,
					it.MultipartHeader().String());
				fSocket->Write(it.MultipartHeader().String(),
					it.MultipartHeader().Length());

				switch (currentField->Type()) {
					default:
					case B_HTTPFORM_UNKNOWN:
						ASSERT(0);
						break;

					case B_HTTPFORM_STRING:
						fSocket->Write(currentField->String().String(),
							currentField->String().Length());
						break;

					case B_HTTPFORM_FILE:
						{
							BFile upFile(currentField->File().Path(),
								B_READ_ONLY);
							char readBuffer[kHttpBufferSize];
							ssize_t readSize;
							off_t totalSize;

							if (upFile.GetSize(&totalSize) != B_OK)
								ASSERT(0);

							readSize = upFile.Read(readBuffer,
								sizeof(readBuffer));
							while (readSize > 0) {
								fSocket->Write(readBuffer, readSize);
								readSize = upFile.Read(readBuffer,
									sizeof(readBuffer));
								fListener->UploadProgress(this, readSize,
									std::max((off_t)0, totalSize));
							}

							break;
						}
					case B_HTTPFORM_BUFFER:
						fSocket->Write(currentField->Buffer(),
							currentField->BufferSize());
						break;
				}

				fSocket->Write("\r\n", 2);
			}

			BString footer = fOptPostFields->GetMultipartFooter();
			fSocket->Write(footer.String(), footer.Length());
		}
	} else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT)
		&& fOptInputData != NULL) {

		// If the input data is seekable, we rewind it for each new request.
		BPositionIO* seekableData
			= dynamic_cast<BPositionIO*>(fOptInputData);
		if (seekableData)
			seekableData->Seek(0, SEEK_SET);

		for (;;) {
			char outputTempBuffer[kHttpBufferSize];
			ssize_t read = fOptInputData->Read(outputTempBuffer,
				sizeof(outputTempBuffer));

			if (read <= 0)
				break;

			if (fOptInputDataSize < 0) {
				// Input data size unknown, so we have to use chunked transfer
				char hexSize[18];
				// The string does not need to be NULL terminated.
				size_t hexLength = snprintf(hexSize, sizeof(hexSize), "%lx\r\n",
					read);

				fSocket->Write(hexSize, hexLength);
				fSocket->Write(outputTempBuffer, read);
				fSocket->Write("\r\n", 2);
			} else {
				fSocket->Write(outputTempBuffer, read);
			}
		}

		if (fOptInputDataSize < 0) {
			// Chunked transfer terminating sequence
			fSocket->Write("0\r\n\r\n", 5);
		}
	}

}


BHttpHeaders&
BHttpRequest::_ResultHeaders()
{
	return fResult.fHeaders;
}


void
BHttpRequest::_SetResultStatusCode(int32 statusCode)
{
	fResult.fStatusCode = statusCode;
}


BString&
BHttpRequest::_ResultStatusText()
{
	return fResult.fStatusString;
}


bool
BHttpRequest::_CertificateVerificationFailed(BCertificate& certificate,
	const char* message)
{
	if (fContext->HasCertificateException(certificate))
		return true;

	if (fListener != NULL
		&& fListener->CertificateVerificationFailed(this, certificate, message)) {
		// User asked us to continue anyway, let's add a temporary exception for this certificate
		fContext->AddCertificateException(certificate);
		return true;
	}

	return false;
}


bool
BHttpRequest::_IsDefaultPort()
{
	if (fSSL && Url().Port() == 443)
		return true;
	if (!fSSL && Url().Port() == 80)
		return true;
	return false;
}