#include "WebAppInterface.h"
#include <Application.h>
#include <Message.h>
#include <Url.h>
#include <AutoDeleter.h>
#include <AutoLocker.h>
#include <HttpHeaders.h>
#include <HttpRequest.h>
#include <Json.h>
#include <JsonMessageWriter.h>
#include <JsonTextWriter.h>
#include <UrlContext.h>
#include <UrlProtocolListener.h>
#include <UrlProtocolRoster.h>
#include "DataIOUtils.h"
#include "HaikuDepotConstants.h"
#include "JwtTokenHelper.h"
#include "Logger.h"
#include "PackageUtils.h"
#include "ServerHelper.h"
#include "ServerSettings.h"
using namespace BPrivate::Network;
#define BASEURL_DEFAULT "https://depot.haiku-os.org"
#define USERAGENT_FALLBACK_VERSION "0.0.0"
#define PROTOCOL_NAME "post-json"
#define LOG_PAYLOAD_LIMIT 8192
class ProtocolListener : public BUrlProtocolListener {
public:
ProtocolListener()
{
}
virtual ~ProtocolListener()
{
}
virtual void ConnectionOpened(BUrlRequest* caller)
{
}
virtual void HostnameResolved(BUrlRequest* caller, const char* ip)
{
}
virtual void ResponseStarted(BUrlRequest* caller)
{
}
virtual void HeadersReceived(BUrlRequest* caller)
{
}
virtual void BytesWritten(BUrlRequest* caller, size_t bytesWritten)
{
}
virtual void DownloadProgress(BUrlRequest* caller, off_t bytesReceived, off_t bytesTotal)
{
}
virtual void UploadProgress(BUrlRequest* caller, off_t bytesSent, off_t bytesTotal)
{
}
virtual void RequestCompleted(BUrlRequest* caller, bool success)
{
}
virtual void DebugMessage(BUrlRequest* caller,
BUrlProtocolDebugMessage type, const char* text)
{
HDTRACE("post-json: %s", text);
}
};
static BHttpRequest*
make_http_request(const BUrl& url, BDataIO* output, BUrlProtocolListener* listener = NULL,
BUrlContext* context = NULL)
{
BUrlRequest* request = BUrlProtocolRoster::MakeRequest(url, output, listener, context);
BHttpRequest* httpRequest = dynamic_cast<BHttpRequest*>(request);
if (httpRequest == NULL) {
delete request;
return NULL;
}
return httpRequest;
}
enum {
NEEDS_AUTHORIZATION = 1 << 0,
};
WebAppInterface::WebAppInterface(const UserCredentials& value)
{
_SetCredentials(value);
}
WebAppInterface::~WebAppInterface()
{
}
void
WebAppInterface::_SetCredentials(const UserCredentials& value)
{
if (fCredentials != value) {
fCredentials = value;
fAccessToken.Clear();
}
}
const BString&
WebAppInterface::Nickname()
{
return fCredentials.Nickname();
}
status_t
WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("pkg/get-pkg-changelog", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::RetrieveUserRatingSummaryForPackage(const BString& packageName,
const BString& webAppRepositoryCode, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(webAppRepositoryCode);
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user-rating/get-summary-by-pkg", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::RetrieveUserRatingsForPackageForDisplay(
const BString& packageName,
const BString& webAppRepositoryCode,
const BString& webAppRepositorySourceCode,
int resultOffset, int maxResults, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectName("offset");
requestEnvelopeWriter.WriteInteger(resultOffset);
requestEnvelopeWriter.WriteObjectName("limit");
requestEnvelopeWriter.WriteInteger(maxResults);
if (!webAppRepositorySourceCode.IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
requestEnvelopeWriter.WriteString(webAppRepositorySourceCode);
}
if (!webAppRepositoryCode.IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(webAppRepositoryCode);
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user-rating/search-user-ratings", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::RetrieveUserRatingForPackageAndVersionByUser(const BString& packageName,
const BPackageVersion& version, const BString& architecture,
const BString& webAppRepositoryCode, const BString& webAppRepositorySourceCode,
const BString& userNickname, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("userNickname");
requestEnvelopeWriter.WriteString(userNickname.String());
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
requestEnvelopeWriter.WriteString(architecture.String());
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(webAppRepositoryCode.String());
requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
requestEnvelopeWriter.WriteString(webAppRepositorySourceCode.String());
if (version.Major().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
requestEnvelopeWriter.WriteString(version.Major().String());
}
if (version.Minor().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
requestEnvelopeWriter.WriteString(version.Minor().String());
}
if (version.Micro().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
requestEnvelopeWriter.WriteString(version.Micro().String());
}
if (version.PreRelease().Length() > 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
requestEnvelopeWriter.WriteString(version.PreRelease().String());
}
if (version.Revision() != 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
requestEnvelopeWriter.WriteInteger(version.Revision());
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user-rating/get-user-rating-by-user-and-pkg-version",
requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
message);
}
status_t
WebAppInterface::RetrieveUserDetailForCredentials(const UserCredentials& credentials,
BMessage& message)
{
if (!credentials.IsValid()) {
debugger("the credentials supplied are invalid so it is not possible "
"to obtain the user detail");
}
status_t result = B_OK;
BMessage authenticateResponseEnvelopeMessage;
if (result == B_OK) {
result = AuthenticateUser(credentials.Nickname(), credentials.PasswordClear(),
authenticateResponseEnvelopeMessage);
}
AccessToken accessToken;
if (result == B_OK)
result = UnpackAccessToken(authenticateResponseEnvelopeMessage, accessToken);
if (result == B_OK) {
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(credentials.Nickname().String());
requestEnvelopeWriter.WriteObjectEnd();
result = _SendJsonRequest("user/get-user", accessToken, requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, message);
}
return result;
}
status_t
WebAppInterface::RetrieveCurrentUserDetail(BMessage& message)
{
UserCredentials credentials = _Credentials();
return RetrieveUserDetailForCredentials(credentials, message);
}
status_t
WebAppInterface::UnpackUserDetail(BMessage& responseEnvelopeMessage, UserDetail& userDetail)
{
BMessage resultMessage;
status_t result = responseEnvelopeMessage.FindMessage("result", &resultMessage);
if (result != B_OK) {
HDERROR("bad response envelope missing 'result' entry");
return result;
}
BString nickname;
result = resultMessage.FindString("nickname", &nickname);
userDetail.SetNickname(nickname);
BMessage agreementMessage;
if (resultMessage.FindMessage("userUsageConditionsAgreement", &agreementMessage) == B_OK) {
BString code;
BDateTime agreedToTimestamp;
BString userUsageConditionsCode;
UserUsageConditionsAgreement agreement = userDetail.Agreement();
bool isLatest;
if (agreementMessage.FindString("userUsageConditionsCode", &userUsageConditionsCode)
== B_OK) {
agreement.SetCode(userUsageConditionsCode);
}
double timestampAgreedMillis;
if (agreementMessage.FindDouble("timestampAgreed", ×tampAgreedMillis) == B_OK)
agreement.SetTimestampAgreed((uint64)timestampAgreedMillis);
if (agreementMessage.FindBool("isLatest", &isLatest) == B_OK)
agreement.SetIsLatest(isLatest);
userDetail.SetAgreement(agreement);
}
return result;
}
status_t
WebAppInterface::UnpackAccessToken(BMessage& responseEnvelopeMessage, AccessToken& accessToken)
{
status_t result;
BMessage resultMessage;
result = responseEnvelopeMessage.FindMessage("result", &resultMessage);
if (result != B_OK) {
HDERROR("bad response envelope missing 'result' entry");
return result;
}
BString token;
result = resultMessage.FindString("token", &token);
if (result != B_OK || token.IsEmpty()) {
HDINFO("failure to authenticate");
return B_PERMISSION_DENIED;
}
BMessage claimsMessage;
result = JwtTokenHelper::ParseClaims(token, claimsMessage);
if (Logger::IsTraceEnabled()) {
HDTRACE("start; token claims...");
claimsMessage.PrintToStream();
HDTRACE("...end; token claims");
}
if (B_OK == result) {
accessToken.SetToken(token);
accessToken.SetExpiryTimestamp(0);
double expiryTimestampDouble;
if (claimsMessage.FindDouble("exp", &expiryTimestampDouble) == B_OK)
accessToken.SetExpiryTimestamp(1000 * static_cast<uint64>(expiryTimestampDouble));
}
return result;
}
status_t
WebAppInterface::RetrieveUserUsageConditions(const BString& code, UserUsageConditions& conditions)
{
BMessage responseEnvelopeMessage;
status_t result = _RetrieveUserUsageConditionsMeta(code, responseEnvelopeMessage);
if (result != B_OK)
return result;
BMessage resultMessage;
if (responseEnvelopeMessage.FindMessage("result", &resultMessage) != B_OK) {
HDERROR("bad response envelope missing 'result' entry");
return B_BAD_DATA;
}
BString metaDataCode;
double metaDataMinimumAge;
BString copyMarkdown;
if ((resultMessage.FindString("code", &metaDataCode) != B_OK)
|| (resultMessage.FindDouble("minimumAge", &metaDataMinimumAge) != B_OK)) {
HDERROR("unexpected response from server with missing user usage "
"conditions data");
return B_BAD_DATA;
}
BMallocIO* copyMarkdownData = new BMallocIO();
result = _RetrieveUserUsageConditionsCopy(metaDataCode, copyMarkdownData);
if (result != B_OK)
return result;
conditions.SetCode(metaDataCode);
conditions.SetMinimumAge(metaDataMinimumAge);
conditions.SetCopyMarkdown(BString(static_cast<const char*>(copyMarkdownData->Buffer()),
copyMarkdownData->BufferLength()));
return B_OK;
}
status_t
WebAppInterface::AgreeUserUsageConditions(const BString& code, BMessage& responsePayload)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
requestEnvelopeWriter.WriteString(code.String());
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(Nickname());
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user/agree-user-usage-conditions", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, responsePayload);
}
status_t
WebAppInterface::_RetrieveUserUsageConditionsMeta(const BString& code, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
if (!code.IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("code");
requestEnvelopeWriter.WriteString(code.String());
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user/get-user-usage-conditions", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::_RetrieveUserUsageConditionsCopy(const BString& code, BDataIO* stream)
{
return _SendRawGetRequest(BString("/__user/usageconditions/") << code << "/document.md",
stream);
}
status_t
WebAppInterface::CreateUserRating(const BString& packageName, const BPackageVersion& version,
const BString& architecture, const BString& webAppRepositoryCode,
const BString& webAppRepositorySourceCode, const BString& naturalLanguageCode,
const BString& comment, const BString& stability, int rating, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("pkgName");
requestEnvelopeWriter.WriteString(packageName.String());
requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
requestEnvelopeWriter.WriteString(architecture.String());
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(webAppRepositoryCode.String());
requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
requestEnvelopeWriter.WriteString(webAppRepositorySourceCode.String());
requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
requestEnvelopeWriter.WriteString(naturalLanguageCode.String());
requestEnvelopeWriter.WriteObjectName("pkgVersionType");
requestEnvelopeWriter.WriteString("SPECIFIC");
requestEnvelopeWriter.WriteObjectName("userNickname");
requestEnvelopeWriter.WriteString(Nickname());
if (!version.Major().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
requestEnvelopeWriter.WriteString(version.Major());
}
if (!version.Minor().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
requestEnvelopeWriter.WriteString(version.Minor());
}
if (!version.Micro().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
requestEnvelopeWriter.WriteString(version.Micro());
}
if (!version.PreRelease().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
requestEnvelopeWriter.WriteString(version.PreRelease());
}
if (version.Revision() != 0) {
requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
requestEnvelopeWriter.WriteInteger(version.Revision());
}
if (rating > 0.0f) {
requestEnvelopeWriter.WriteObjectName("rating");
requestEnvelopeWriter.WriteInteger(rating);
}
if (stability.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
requestEnvelopeWriter.WriteString(stability);
}
if (comment.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("comment");
requestEnvelopeWriter.WriteString(comment.String());
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user-rating/create-user-rating", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, message);
}
status_t
WebAppInterface::UpdateUserRating(const BString& ratingID, const BString& naturalLanguageCode,
const BString& comment, const BString& stability, int rating, bool active, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("code");
requestEnvelopeWriter.WriteString(ratingID.String());
requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
requestEnvelopeWriter.WriteString(naturalLanguageCode.String());
requestEnvelopeWriter.WriteObjectName("active");
requestEnvelopeWriter.WriteBoolean(active);
requestEnvelopeWriter.WriteObjectName("filter");
requestEnvelopeWriter.WriteArrayStart();
requestEnvelopeWriter.WriteString("ACTIVE");
requestEnvelopeWriter.WriteString("NATURALLANGUAGE");
requestEnvelopeWriter.WriteString("USERRATINGSTABILITY");
requestEnvelopeWriter.WriteString("COMMENT");
requestEnvelopeWriter.WriteString("RATING");
requestEnvelopeWriter.WriteArrayEnd();
if (rating >= 0) {
requestEnvelopeWriter.WriteObjectName("rating");
requestEnvelopeWriter.WriteInteger(rating);
}
if (stability.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
requestEnvelopeWriter.WriteString(stability);
}
if (comment.Length() > 0) {
requestEnvelopeWriter.WriteObjectName("comment");
requestEnvelopeWriter.WriteString(comment);
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user-rating/update-user-rating", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, message);
}
status_t
WebAppInterface::RetrieveScreenshot(const BString& code, int32 width, int32 height, BDataIO* stream)
{
return _SendRawGetRequest(BString("/__pkgscreenshot/")
<< code << ".png" << "?tw=" << width << "&th=" << height,
stream);
}
status_t
WebAppInterface::RequestCaptcha(BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("captcha/generate-captcha", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::CreateUser(const BString& nickName, const BString& passwordClear,
const BString& email, const BString& captchaToken, const BString& captchaResponse,
const BString& naturalLanguageCode,
const BString& userUsageConditionsCode, BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(nickName.String());
requestEnvelopeWriter.WriteObjectName("passwordClear");
requestEnvelopeWriter.WriteString(passwordClear.String());
requestEnvelopeWriter.WriteObjectName("captchaToken");
requestEnvelopeWriter.WriteString(captchaToken.String());
requestEnvelopeWriter.WriteObjectName("captchaResponse");
requestEnvelopeWriter.WriteString(captchaResponse.String());
requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
requestEnvelopeWriter.WriteString(naturalLanguageCode.String());
requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
requestEnvelopeWriter.WriteString(userUsageConditionsCode.String());
if (!email.IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("email");
requestEnvelopeWriter.WriteString(email.String());
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user/create-user", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::AuthenticateUserRetainingAccessToken()
{
UserCredentials userCredentials = _Credentials();
if (!userCredentials.IsValid()) {
HDINFO("unable to get a new access token as there are no credentials");
return B_NOT_INITIALIZED;
}
return _AuthenticateUserRetainingAccessToken(userCredentials.Nickname(),
userCredentials.PasswordClear());
}
status_t
WebAppInterface::_AuthenticateUserRetainingAccessToken(const BString& nickName,
const BString& passwordClear)
{
AutoLocker<BLocker> lock(&fLock);
fAccessToken.Clear();
BMessage responseEnvelopeMessage;
status_t result = AuthenticateUser(nickName, passwordClear, responseEnvelopeMessage);
AccessToken accessToken;
if (result == B_OK)
result = UnpackAccessToken(responseEnvelopeMessage, accessToken);
if (result == B_OK)
fAccessToken = accessToken;
return result;
}
status_t
WebAppInterface::AuthenticateUser(const BString& nickName, const BString& passwordClear,
BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("nickname");
requestEnvelopeWriter.WriteString(nickName.String());
requestEnvelopeWriter.WriteObjectName("passwordClear");
requestEnvelopeWriter.WriteString(passwordClear.String());
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user/authenticate-user", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::IncrementViewCounter(const PackageInfoRef package, const DepotInfoRef depot,
BMessage& message)
{
if (!package.IsSet()) {
HDERROR("unable to increment the view count as no package has been provided.");
return B_BAD_DATA;
}
const PackageVersionRef version = PackageUtils::Version(package);
if (!version.IsSet()) {
HDERROR("unable to increment the view count as no version has been set.");
return B_BAD_DATA;
}
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectName("architectureCode");
requestEnvelopeWriter.WriteString(PackageUtils::Architecture(package));
requestEnvelopeWriter.WriteObjectName("repositoryCode");
requestEnvelopeWriter.WriteString(depot->WebAppRepositoryCode());
requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
requestEnvelopeWriter.WriteString(depot->WebAppRepositorySourceCode());
requestEnvelopeWriter.WriteObjectName("name");
requestEnvelopeWriter.WriteString(package->Name());
if (!version->Major().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("major");
requestEnvelopeWriter.WriteString(version->Major());
}
if (!version->Minor().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("minor");
requestEnvelopeWriter.WriteString(version->Minor());
}
if (!version->Micro().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("micro");
requestEnvelopeWriter.WriteString(version->Micro());
}
if (!version->PreRelease().IsEmpty()) {
requestEnvelopeWriter.WriteObjectName("preRelease");
requestEnvelopeWriter.WriteString(version->PreRelease());
}
if (version->Revision() != 0) {
requestEnvelopeWriter.WriteObjectName("revision");
requestEnvelopeWriter.WriteInteger(static_cast<int64>(version->Revision()));
}
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("pkg/increment-view-counter", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
status_t
WebAppInterface::RetrievePasswordRequirements(PasswordRequirements& passwordRequirements)
{
BMessage responseEnvelopeMessage;
status_t result = _RetrievePasswordRequirementsMeta(responseEnvelopeMessage);
if (result != B_OK)
return result;
BMessage resultMessage;
result = responseEnvelopeMessage.FindMessage("result", &resultMessage);
if (result != B_OK) {
HDERROR("bad response envelope missing 'result' entry");
return result;
}
double value;
if (resultMessage.FindDouble("minPasswordLength", &value) == B_OK)
passwordRequirements.SetMinPasswordLength((uint32)value);
if (resultMessage.FindDouble("minPasswordUppercaseChar", &value) == B_OK)
passwordRequirements.SetMinPasswordUppercaseChar((uint32)value);
if (resultMessage.FindDouble("minPasswordDigitsChar", &value) == B_OK)
passwordRequirements.SetMinPasswordDigitsChar((uint32)value);
return result;
}
status_t
WebAppInterface::_RetrievePasswordRequirementsMeta(BMessage& message)
{
BMallocIO* requestEnvelopeData = new BMallocIO();
BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
requestEnvelopeWriter.WriteObjectStart();
requestEnvelopeWriter.WriteObjectEnd();
return _SendJsonRequest("user/get-password-requirements", requestEnvelopeData,
_LengthAndSeekToZero(requestEnvelopeData), 0, message);
}
int32
WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
{
BMessage error;
double code;
if (responseEnvelopeMessage.FindMessage("error", &error) == B_OK
&& error.FindDouble("code", &code) == B_OK) {
return (int32)code;
}
return 0;
}
status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents, BPositionIO* requestData,
size_t requestDataSize, uint32 flags, BMessage& reply)
{
bool needsAuthorization = (flags & NEEDS_AUTHORIZATION) != 0;
AccessToken accessToken;
if (needsAuthorization)
accessToken = _ObtainValidAccessToken();
return _SendJsonRequest(urlPathComponents, accessToken, requestData, requestDataSize, flags,
reply);
}
status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents, const AccessToken& accessToken,
BPositionIO* requestData, size_t requestDataSize, uint32 flags, BMessage& reply)
{
if (requestDataSize == 0) {
HDINFO("%s; empty request payload", PROTOCOL_NAME);
return B_ERROR;
}
if (!ServerHelper::IsNetworkAvailable()) {
HDDEBUG("%s; dropping request to ...[%s] as network is not"
" available",
PROTOCOL_NAME, urlPathComponents);
delete requestData;
return HD_NETWORK_INACCESSIBLE;
}
if (ServerSettings::IsClientTooOld()) {
HDDEBUG("%s; dropping request to ...[%s] as client is too old", PROTOCOL_NAME,
urlPathComponents);
delete requestData;
return HD_CLIENT_TOO_OLD;
}
bool needsAuthorization = (flags & NEEDS_AUTHORIZATION) != 0;
if (needsAuthorization && !accessToken.IsValid()) {
HDDEBUG("%s; dropping request to ...[%s] as access token is not valid", PROTOCOL_NAME,
urlPathComponents);
delete requestData;
return B_NOT_ALLOWED;
}
BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v2/") << urlPathComponents);
HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME, url.UrlString().String());
if (Logger::IsTraceEnabled()) {
HDLOGPREFIX(LOG_LEVEL_TRACE)
printf("%s request; ", PROTOCOL_NAME);
_LogPayload(requestData, requestDataSize);
printf("\n");
}
ProtocolListener listener;
BUrlContext context;
BHttpHeaders headers;
headers.AddHeader("Content-Type", "application/json");
headers.AddHeader("Accept", "application/json");
ServerSettings::AugmentHeaders(headers);
BHttpRequest* request = make_http_request(url, NULL, &listener, &context);
ObjectDeleter<BHttpRequest> _(request);
if (request == NULL)
return B_ERROR;
request->SetMethod(B_HTTP_POST);
request->SetHeaders(headers);
if (needsAuthorization) {
BHttpAuthentication authentication;
authentication.SetMethod(B_HTTP_AUTHENTICATION_BEARER);
authentication.SetToken(accessToken.Token());
context.AddAuthentication(url, authentication);
}
request->AdoptInputData(requestData, requestDataSize);
BMallocIO replyData;
request->SetOutput(&replyData);
thread_id thread = request->Run();
wait_for_thread(thread, NULL);
const BHttpResult& result = dynamic_cast<const BHttpResult&>(request->Result());
int32 statusCode = result.StatusCode();
HDDEBUG("%s; did receive http-status [%" B_PRId32 "] from [%s]", PROTOCOL_NAME, statusCode,
url.UrlString().String());
switch (statusCode) {
case B_HTTP_STATUS_OK:
break;
case B_HTTP_STATUS_PRECONDITION_FAILED:
ServerHelper::NotifyClientTooOld(result.Headers());
return HD_CLIENT_TOO_OLD;
default:
HDERROR("%s; request to endpoint [.../%s] failed with http "
"status [%" B_PRId32 "]\n",
PROTOCOL_NAME, urlPathComponents, statusCode);
return B_ERROR;
}
replyData.Seek(0, SEEK_SET);
if (Logger::IsTraceEnabled()) {
HDLOGPREFIX(LOG_LEVEL_TRACE)
printf("%s; response; ", PROTOCOL_NAME);
_LogPayload(&replyData, replyData.BufferLength());
printf("\n");
}
BJsonMessageWriter jsonMessageWriter(reply);
BJson::Parse(&replyData, &jsonMessageWriter);
status_t status = jsonMessageWriter.ErrorStatus();
if (Logger::IsTraceEnabled() && status == B_BAD_DATA) {
BString resultString(static_cast<const char*>(replyData.Buffer()),
replyData.BufferLength());
HDERROR("Parser choked on JSON:\n%s", resultString.String());
}
return status;
}
status_t
WebAppInterface::_SendJsonRequest(const char* urlPathComponents, const BString& jsonString,
uint32 flags, BMessage& reply)
{
BMemoryIO* data = new BMemoryIO(jsonString.String(), jsonString.Length() - 1);
return _SendJsonRequest(urlPathComponents, data, jsonString.Length() - 1, flags, reply);
}
status_t
WebAppInterface::_SendRawGetRequest(const BString urlPathComponents, BDataIO* stream)
{
BUrl url = ServerSettings::CreateFullUrl(urlPathComponents);
HDDEBUG("http-get; will make request to [%s]", url.UrlString().String());
ProtocolListener listener;
BHttpHeaders headers;
ServerSettings::AugmentHeaders(headers);
BHttpRequest* request = make_http_request(url, stream, &listener);
ObjectDeleter<BHttpRequest> _(request);
if (request == NULL)
return B_ERROR;
request->SetMethod(B_HTTP_GET);
request->SetHeaders(headers);
thread_id thread = request->Run();
wait_for_thread(thread, NULL);
const BHttpResult& result = dynamic_cast<const BHttpResult&>(request->Result());
int32 statusCode = result.StatusCode();
HDDEBUG("http-get; did receive http-status [%" B_PRId32 "] from [%s]", statusCode,
url.UrlString().String());
if (statusCode == 200)
return B_OK;
HDERROR("failed to get data from '%s': %" B_PRIi32 "", url.UrlString().String(), statusCode);
return B_ERROR;
}
void
WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size)
{
off_t requestDataOffset = requestData->Position();
char buffer[LOG_PAYLOAD_LIMIT];
if (size > LOG_PAYLOAD_LIMIT)
size = LOG_PAYLOAD_LIMIT;
if (B_OK != requestData->ReadExactly(buffer, size)) {
printf("%s; error logging payload", PROTOCOL_NAME);
} else {
for (uint32 i = 0; i < size; i++) {
bool esc = buffer[i] > 126 || (buffer[i] < 0x20 && buffer[i] != 0x0a);
if (esc)
printf("\\u%02x", buffer[i]);
else
putchar(buffer[i]);
}
if (size == LOG_PAYLOAD_LIMIT)
printf("...(continues)");
}
requestData->Seek(requestDataOffset, SEEK_SET);
}
off_t
WebAppInterface::_LengthAndSeekToZero(BPositionIO* data)
{
off_t dataSize = data->Position();
data->Seek(0, SEEK_SET);
return dataSize;
}
UserCredentials
WebAppInterface::_Credentials()
{
return fCredentials;
}
AccessToken
WebAppInterface::_ObtainValidAccessToken()
{
AutoLocker<BLocker> lock(&fLock);
uint64 now = static_cast<uint64>(time(NULL)) * 1000;
if (!fAccessToken.IsValid(now)) {
HDINFO("clearing cached access token as it is no longer valid");
fAccessToken.Clear();
}
if (!fAccessToken.IsValid()) {
HDINFO("no cached access token present; will obtain a new one");
AuthenticateUserRetainingAccessToken();
}
return fAccessToken;
}