* 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();
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)
{
if (fRequestStatus == kRequestInitialState)
fOptRangeStart = position;
}
void
BHttpRequest::SetRangeEnd(off_t position)
{
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;
}
bool
BHttpRequest::IsInformationalStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__INFORMATIONAL_BASE)
&& (code < B_HTTP_STATUS__INFORMATIONAL_END);
}
bool
BHttpRequest::IsSuccessStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__SUCCESS_BASE)
&& (code < B_HTTP_STATUS__SUCCESS_END);
}
bool
BHttpRequest::IsRedirectionStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__REDIRECTION_BASE)
&& (code < B_HTTP_STATUS__REDIRECTION_END);
}
bool
BHttpRequest::IsClientErrorStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__CLIENT_ERROR_BASE)
&& (code < B_HTTP_STATUS__CLIENT_ERROR_END);
}
bool
BHttpRequest::IsServerErrorStatusCode(int16 code)
{
return (code >= B_HTTP_STATUS__SERVER_ERROR_BASE)
&& (code < B_HTTP_STATUS__SERVER_ERROR_END);
}
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();
}
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()
{
int8 maxRedirs = fOptMaxRedirs;
bool newRequest;
do {
newRequest = false;
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;
if (fOptAutoReferer)
fOptReferer = fUrl.UrlString();
switch (StatusCodeClass(fResult.StatusCode())) {
case B_HTTP_STATUS_CLASS_INFORMATIONAL:
break;
case B_HTTP_STATUS_CLASS_SUCCESS:
break;
case B_HTTP_STATUS_CLASS_REDIRECTION:
{
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);
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;
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) {
BHttpAuthentication newAuth;
newAuth.Initialize(fHeaders["WWW-Authenticate"]);
fContext->AddAuthentication(fUrl, newAuth);
authentication = &fContext->GetAuthentication(fUrl);
}
newRequest = false;
if (fOptUsername.Length() > 0 && status == B_OK) {
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;
}
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;
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) {
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;
}
if (fRequestStatus >= kRequestStatusReceived && fListener != NULL
&& !disableListener)
fListener->ResponseStarted(this);
}
if (fRequestStatus < kRequestHeadersReceived) {
_ParseHeaders();
if (fRequestStatus >= kRequestHeadersReceived) {
_ResultHeaders() = fHeaders;
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);
}
}
}
if (fListener != NULL && !disableListener)
fListener->HeadersReceived(this);
if (BString(fHeaders["Transfer-Encoding"]) == "chunked")
readByChunks = true;
BString contentEncoding(fHeaders["Content-Encoding"]);
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) {
receiveEnd = true;
}
}
}
if (fRequestStatus >= kRequestHeadersReceived) {
if (readByChunks) {
if (chunkSize >= 0) {
if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) {
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 {
bytesRead = -1;
}
} else {
BString chunkHeader;
if (_GetLine(chunkHeader) == B_ERROR) {
chunkSize = -1;
bytesRead = -1;
} else {
int32 semiColonIndex = chunkHeader.FindFirst(';', 0);
if (semiColonIndex != -1) {
chunkHeader.Remove(semiColonIndex,
chunkHeader.Length() - semiColonIndex);
}
chunkSize = strtol(chunkHeader.String(), NULL, 16);
if (chunkSize == 0)
fRequestStatus = kRequestContentReceived;
bytesRead = -1;
}
}
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()
{
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) {
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()) {
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;
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");
outputHeaders.AddHeader("Connection", "close");
}
if (fOptUserAgent.CountChars() > 0)
outputHeaders.AddHeader("User-Agent", fOptUserAgent.String());
if (fOptReferer.CountChars() > 0)
outputHeaders.AddHeader("Referer", fOptReferer.String());
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());
}
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));
}
}
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");
}
if (fOptHeaders != NULL) {
for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
headerIndex++) {
BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
int32 replaceIndex = outputHeaders.HasHeader(optHeader.Name());
if (replaceIndex == -1)
outputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
else
outputHeaders[replaceIndex].SetValue(optHeader.Value());
}
}
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);
}
}
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) {
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) {
char hexSize[18];
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) {
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)) {
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;
}