/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 inputTempBufferDeleter(inputTempBuffer); ssize_t inputTempSize = kHttpBufferSize; ssize_t chunkSize = -1; DynamicBuffer decompressorStorage; BDataIO* decompressingStream; ObjectDeleter decompressingStreamDeleter; while (!fQuit && !(receiveEnd && parseEnd)) { if ((!receiveEnd) && (fInputBuffer.Size() == previousBufferSize)) { BStackOrHeapArray 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: // [; 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 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 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(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; }