xref: /haiku/src/apps/haikudepot/server/WebAppInterface.cpp (revision 4b3c808edaaf626e7ee6cb0ee9d21dc96b173ee6)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2016-2024, Andrew Lindesay <apl@lindesay.co.nz>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 #include "WebAppInterface.h"
7 
8 #include <Application.h>
9 #include <Message.h>
10 #include <Url.h>
11 
12 #include <AutoDeleter.h>
13 #include <AutoLocker.h>
14 #include <HttpHeaders.h>
15 #include <HttpRequest.h>
16 #include <Json.h>
17 #include <JsonTextWriter.h>
18 #include <JsonMessageWriter.h>
19 #include <UrlContext.h>
20 #include <UrlProtocolListener.h>
21 #include <UrlProtocolRoster.h>
22 
23 #include "DataIOUtils.h"
24 #include "HaikuDepotConstants.h"
25 #include "JwtTokenHelper.h"
26 #include "Logger.h"
27 #include "ServerSettings.h"
28 #include "ServerHelper.h"
29 
30 
31 using namespace BPrivate::Network;
32 
33 
34 #define BASEURL_DEFAULT "https://depot.haiku-os.org"
35 #define USERAGENT_FALLBACK_VERSION "0.0.0"
36 #define PROTOCOL_NAME "post-json"
37 #define LOG_PAYLOAD_LIMIT 8192
38 
39 
40 class ProtocolListener : public BUrlProtocolListener {
41 public:
ProtocolListener()42 	ProtocolListener()
43 	{
44 	}
45 
~ProtocolListener()46 	virtual ~ProtocolListener()
47 	{
48 	}
49 
ConnectionOpened(BUrlRequest * caller)50 	virtual	void ConnectionOpened(BUrlRequest* caller)
51 	{
52 	}
53 
HostnameResolved(BUrlRequest * caller,const char * ip)54 	virtual void HostnameResolved(BUrlRequest* caller, const char* ip)
55 	{
56 	}
57 
ResponseStarted(BUrlRequest * caller)58 	virtual void ResponseStarted(BUrlRequest* caller)
59 	{
60 	}
61 
HeadersReceived(BUrlRequest * caller)62 	virtual void HeadersReceived(BUrlRequest* caller)
63 	{
64 	}
65 
BytesWritten(BUrlRequest * caller,size_t bytesWritten)66 	virtual void BytesWritten(BUrlRequest* caller, size_t bytesWritten)
67 	{
68 	}
69 
DownloadProgress(BUrlRequest * caller,off_t bytesReceived,off_t bytesTotal)70 	virtual	void DownloadProgress(BUrlRequest* caller, off_t bytesReceived, off_t bytesTotal)
71 	{
72 	}
73 
UploadProgress(BUrlRequest * caller,off_t bytesSent,off_t bytesTotal)74 	virtual void UploadProgress(BUrlRequest* caller, off_t bytesSent, off_t bytesTotal)
75 	{
76 	}
77 
RequestCompleted(BUrlRequest * caller,bool success)78 	virtual void RequestCompleted(BUrlRequest* caller, bool success)
79 	{
80 	}
81 
DebugMessage(BUrlRequest * caller,BUrlProtocolDebugMessage type,const char * text)82 	virtual void DebugMessage(BUrlRequest* caller,
83 		BUrlProtocolDebugMessage type, const char* text)
84 	{
85 		HDTRACE("post-json: %s", text);
86 	}
87 };
88 
89 
90 static BHttpRequest*
make_http_request(const BUrl & url,BDataIO * output,BUrlProtocolListener * listener=NULL,BUrlContext * context=NULL)91 make_http_request(const BUrl& url, BDataIO* output,
92 	BUrlProtocolListener* listener = NULL,
93 	BUrlContext* context = NULL)
94 {
95 	BUrlRequest* request = BUrlProtocolRoster::MakeRequest(url, output,
96 		listener, context);
97 	BHttpRequest* httpRequest = dynamic_cast<BHttpRequest*>(request);
98 	if (httpRequest == NULL) {
99 		delete request;
100 		return NULL;
101 	}
102 	return httpRequest;
103 }
104 
105 
106 enum {
107 	NEEDS_AUTHORIZATION = 1 << 0,
108 };
109 
110 
WebAppInterface()111 WebAppInterface::WebAppInterface()
112 {
113 }
114 
115 
~WebAppInterface()116 WebAppInterface::~WebAppInterface()
117 {
118 }
119 
120 
121 void
SetCredentials(const UserCredentials & value)122 WebAppInterface::SetCredentials(const UserCredentials& value)
123 {
124 	AutoLocker<BLocker> lock(&fLock);
125 	if (fCredentials != value) {
126 		fCredentials = value;
127 		fAccessToken.Clear();
128 	}
129 }
130 
131 
132 const BString&
Nickname()133 WebAppInterface::Nickname()
134 {
135 	AutoLocker<BLocker> lock(&fLock);
136 	return fCredentials.Nickname();
137 }
138 
139 
140 status_t
GetChangelog(const BString & packageName,BMessage & message)141 WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
142 {
143 	BMallocIO* requestEnvelopeData = new BMallocIO();
144 		// BHttpRequest later takes ownership of this.
145 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
146 
147 	requestEnvelopeWriter.WriteObjectStart();
148 	requestEnvelopeWriter.WriteObjectName("pkgName");
149 	requestEnvelopeWriter.WriteString(packageName.String());
150 	requestEnvelopeWriter.WriteObjectEnd();
151 
152 	return _SendJsonRequest("pkg/get-pkg-changelog", requestEnvelopeData,
153 		_LengthAndSeekToZero(requestEnvelopeData), 0, message);
154 }
155 
156 
157 /*!	The summary is provided for by an algorithm on the server side. The
158 	derivation is performed on the server because it follows an
159 	algorithm that requires settings and rules.
160 */
161 
162 status_t
RetrieveUserRatingSummaryForPackage(const BString & packageName,const BString & webAppRepositoryCode,BMessage & message)163 WebAppInterface::RetrieveUserRatingSummaryForPackage(const BString& packageName,
164 	const BString& webAppRepositoryCode, BMessage& message)
165 {
166 	// BHttpRequest later takes ownership of this.
167 	BMallocIO* requestEnvelopeData = new BMallocIO();
168 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
169 
170 	requestEnvelopeWriter.WriteObjectStart();
171 	requestEnvelopeWriter.WriteObjectName("pkgName");
172 	requestEnvelopeWriter.WriteString(packageName.String());
173 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
174 	requestEnvelopeWriter.WriteString(webAppRepositoryCode);
175 
176 	requestEnvelopeWriter.WriteObjectEnd();
177 
178 	return _SendJsonRequest("user-rating/get-summary-by-pkg", requestEnvelopeData,
179 		_LengthAndSeekToZero(requestEnvelopeData), 0, message);
180 }
181 
182 
183 status_t
RetrieveUserRatingsForPackageForDisplay(const BString & packageName,const BString & webAppRepositoryCode,const BString & webAppRepositorySourceCode,int resultOffset,int maxResults,BMessage & message)184 WebAppInterface::RetrieveUserRatingsForPackageForDisplay(
185 	const BString& packageName,
186 	const BString& webAppRepositoryCode,
187 	const BString& webAppRepositorySourceCode,
188 	int resultOffset, int maxResults, BMessage& message)
189 {
190 	// BHttpRequest later takes ownership of this.
191 	BMallocIO* requestEnvelopeData = new BMallocIO();
192 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
193 
194 	requestEnvelopeWriter.WriteObjectStart();
195 	requestEnvelopeWriter.WriteObjectName("pkgName");
196 	requestEnvelopeWriter.WriteString(packageName.String());
197 	requestEnvelopeWriter.WriteObjectName("offset");
198 	requestEnvelopeWriter.WriteInteger(resultOffset);
199 	requestEnvelopeWriter.WriteObjectName("limit");
200 	requestEnvelopeWriter.WriteInteger(maxResults);
201 
202 	if (!webAppRepositorySourceCode.IsEmpty()) {
203 		requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
204 		requestEnvelopeWriter.WriteString(webAppRepositorySourceCode);
205 	}
206 
207 	if (!webAppRepositoryCode.IsEmpty()) {
208 		requestEnvelopeWriter.WriteObjectName("repositoryCode");
209 		requestEnvelopeWriter.WriteString(webAppRepositoryCode);
210 	}
211 
212 	requestEnvelopeWriter.WriteObjectEnd();
213 
214 	return _SendJsonRequest("user-rating/search-user-ratings",
215 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
216 		0, message);
217 }
218 
219 
220 status_t
RetrieveUserRatingForPackageAndVersionByUser(const BString & packageName,const BPackageVersion & version,const BString & architecture,const BString & webAppRepositoryCode,const BString & webAppRepositorySourceCode,const BString & userNickname,BMessage & message)221 WebAppInterface::RetrieveUserRatingForPackageAndVersionByUser(
222 	const BString& packageName, const BPackageVersion& version,
223 	const BString& architecture,
224 	const BString& webAppRepositoryCode,
225 	const BString& webAppRepositorySourceCode,
226 	const BString& userNickname, BMessage& message)
227 {
228 		// BHttpRequest later takes ownership of this.
229 	BMallocIO* requestEnvelopeData = new BMallocIO();
230 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
231 
232 	requestEnvelopeWriter.WriteObjectStart();
233 
234 	requestEnvelopeWriter.WriteObjectName("userNickname");
235 	requestEnvelopeWriter.WriteString(userNickname.String());
236 	requestEnvelopeWriter.WriteObjectName("pkgName");
237 	requestEnvelopeWriter.WriteString(packageName.String());
238 	requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
239 	requestEnvelopeWriter.WriteString(architecture.String());
240 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
241 	requestEnvelopeWriter.WriteString(webAppRepositoryCode.String());
242 	requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
243 	requestEnvelopeWriter.WriteString(webAppRepositorySourceCode.String());
244 
245 	if (version.Major().Length() > 0) {
246 		requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
247 		requestEnvelopeWriter.WriteString(version.Major().String());
248 	}
249 
250 	if (version.Minor().Length() > 0) {
251 		requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
252 		requestEnvelopeWriter.WriteString(version.Minor().String());
253 	}
254 
255 	if (version.Micro().Length() > 0) {
256 		requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
257 		requestEnvelopeWriter.WriteString(version.Micro().String());
258 	}
259 
260 	if (version.PreRelease().Length() > 0) {
261 		requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
262 		requestEnvelopeWriter.WriteString(version.PreRelease().String());
263 	}
264 
265 	if (version.Revision() != 0) {
266 		requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
267 		requestEnvelopeWriter.WriteInteger(version.Revision());
268 	}
269 
270 	requestEnvelopeWriter.WriteObjectEnd();
271 
272 	return _SendJsonRequest(
273 		"user-rating/get-user-rating-by-user-and-pkg-version",
274 		requestEnvelopeData,
275 		_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
276 		message);
277 }
278 
279 
280 /*!	This method will fill out the supplied UserDetail object with information
281 	about the user that is supplied in the credentials.  Importantly it will
282 	also authenticate the request with the details of the credentials and will
283 	not use the credentials that are configured in 'fCredentials'.
284 */
285 
286 status_t
RetrieveUserDetailForCredentials(const UserCredentials & credentials,BMessage & message)287 WebAppInterface::RetrieveUserDetailForCredentials(
288 	const UserCredentials& credentials, BMessage& message)
289 {
290 	if (!credentials.IsValid()) {
291 		debugger("the credentials supplied are invalid so it is not possible "
292 			"to obtain the user detail");
293 	}
294 
295 	status_t result = B_OK;
296 
297 	// authenticate the user and obtain a token to use with the latter
298 	// request.
299 
300 	BMessage authenticateResponseEnvelopeMessage;
301 
302 	if (result == B_OK) {
303 		result = AuthenticateUser(
304 			credentials.Nickname(),
305 			credentials.PasswordClear(),
306 			authenticateResponseEnvelopeMessage);
307 	}
308 
309 	AccessToken accessToken;
310 
311 	if (result == B_OK)
312 		result = UnpackAccessToken(authenticateResponseEnvelopeMessage, accessToken);
313 
314 	if (result == B_OK) {
315 			// BHttpRequest later takes ownership of this.
316 		BMallocIO* requestEnvelopeData = new BMallocIO();
317 		BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
318 
319 		requestEnvelopeWriter.WriteObjectStart();
320 		requestEnvelopeWriter.WriteObjectName("nickname");
321 		requestEnvelopeWriter.WriteString(credentials.Nickname().String());
322 		requestEnvelopeWriter.WriteObjectEnd();
323 
324 		result = _SendJsonRequest("user/get-user", accessToken,
325 			requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
326 			NEEDS_AUTHORIZATION, message);
327 			// note that the credentials used here are passed in as args.
328 	}
329 
330 	return result;
331 }
332 
333 
334 /*!	This method will return the credentials for the currently authenticated
335 	user.
336 */
337 
338 status_t
RetrieveCurrentUserDetail(BMessage & message)339 WebAppInterface::RetrieveCurrentUserDetail(BMessage& message)
340 {
341 	UserCredentials credentials = _Credentials();
342 	return RetrieveUserDetailForCredentials(credentials, message);
343 }
344 
345 
346 /*!	When the user requests user detail, the server sends back an envelope of
347 	response data.  This method will unpack the data into a model object.
348 	\return Not B_OK if something went wrong.
349 */
350 
351 /*static*/ status_t
UnpackUserDetail(BMessage & responseEnvelopeMessage,UserDetail & userDetail)352 WebAppInterface::UnpackUserDetail(BMessage& responseEnvelopeMessage,
353 	UserDetail& userDetail)
354 {
355 	BMessage resultMessage;
356 	status_t result = responseEnvelopeMessage.FindMessage(
357 		"result", &resultMessage);
358 
359 	if (result != B_OK) {
360 		HDERROR("bad response envelope missing 'result' entry");
361 		return result;
362 	}
363 
364 	BString nickname;
365 	result = resultMessage.FindString("nickname", &nickname);
366 	userDetail.SetNickname(nickname);
367 
368 	BMessage agreementMessage;
369 	if (resultMessage.FindMessage("userUsageConditionsAgreement",
370 		&agreementMessage) == B_OK) {
371 		BString code;
372 		BDateTime agreedToTimestamp;
373 		BString userUsageConditionsCode;
374 		UserUsageConditionsAgreement agreement = userDetail.Agreement();
375 		bool isLatest;
376 
377 		if (agreementMessage.FindString("userUsageConditionsCode",
378 			&userUsageConditionsCode) == B_OK) {
379 			agreement.SetCode(userUsageConditionsCode);
380 		}
381 
382 		double timestampAgreedMillis;
383 		if (agreementMessage.FindDouble("timestampAgreed",
384 			&timestampAgreedMillis) == B_OK) {
385 			agreement.SetTimestampAgreed((uint64) timestampAgreedMillis);
386 		}
387 
388 		if (agreementMessage.FindBool("isLatest", &isLatest)
389 			== B_OK) {
390 			agreement.SetIsLatest(isLatest);
391 		}
392 
393 		userDetail.SetAgreement(agreement);
394 	}
395 
396 	return result;
397 }
398 
399 
400 /*! When an authentication API call is made, the response (if successful) will
401     return an access token in the response. This method will take the response
402     from the server and will parse out the access token data into the supplied
403     object.
404 */
405 
406 /*static*/ status_t
UnpackAccessToken(BMessage & responseEnvelopeMessage,AccessToken & accessToken)407 WebAppInterface::UnpackAccessToken(BMessage& responseEnvelopeMessage,
408 	AccessToken& accessToken)
409 {
410 	status_t result;
411 
412 	BMessage resultMessage;
413 	result = responseEnvelopeMessage.FindMessage(
414 		"result", &resultMessage);
415 
416 	if (result != B_OK) {
417 		HDERROR("bad response envelope missing 'result' entry");
418 		return result;
419 	}
420 
421 	BString token;
422 	result = resultMessage.FindString("token", &token);
423 
424 	if (result != B_OK || token.IsEmpty()) {
425 		HDINFO("failure to authenticate");
426 		return B_PERMISSION_DENIED;
427 	}
428 
429 	// The token should be present in three parts; the header, the claims and
430 	// then a digital signature. The logic here wants to extract some data
431 	// from the claims part.
432 
433 	BMessage claimsMessage;
434 	result = JwtTokenHelper::ParseClaims(token, claimsMessage);
435 
436 	if (Logger::IsTraceEnabled()) {
437 		HDTRACE("start; token claims...");
438 		claimsMessage.PrintToStream();
439 		HDTRACE("...end; token claims");
440 	}
441 
442 	if (B_OK == result) {
443 		accessToken.SetToken(token);
444 		accessToken.SetExpiryTimestamp(0);
445 
446 		double expiryTimestampDouble;
447 
448 		// The claims should have parsed but it could transpire that there is
449 		// no expiry. This should not be the case, but it is theoretically
450 		// possible.
451 
452 		if (claimsMessage.FindDouble("exp", &expiryTimestampDouble) == B_OK)
453 			accessToken.SetExpiryTimestamp(1000 * static_cast<uint64>(expiryTimestampDouble));
454 	}
455 
456 	return result;
457 }
458 
459 
460 /*!	\brief Returns data relating to the user usage conditions
461 
462 	\param code defines the version of the data to return or if empty then the
463 		latest is returned.
464 
465 	This method will go to the server and get details relating to the user usage
466 	conditions.  It does this in two API calls; first gets the details (the
467 	minimum age) and in the second call, the text of the conditions is returned.
468 */
469 
470 status_t
RetrieveUserUsageConditions(const BString & code,UserUsageConditions & conditions)471 WebAppInterface::RetrieveUserUsageConditions(const BString& code,
472 	UserUsageConditions& conditions)
473 {
474 	BMessage responseEnvelopeMessage;
475 	status_t result = _RetrieveUserUsageConditionsMeta(code,
476 		responseEnvelopeMessage);
477 
478 	if (result != B_OK)
479 		return result;
480 
481 	BMessage resultMessage;
482 	if (responseEnvelopeMessage.FindMessage("result", &resultMessage) != B_OK) {
483 		HDERROR("bad response envelope missing 'result' entry");
484 		return B_BAD_DATA;
485 	}
486 
487 	BString metaDataCode;
488 	double metaDataMinimumAge;
489 	BString copyMarkdown;
490 
491 	if ( (resultMessage.FindString("code", &metaDataCode) != B_OK)
492 			|| (resultMessage.FindDouble(
493 				"minimumAge", &metaDataMinimumAge) != B_OK) ) {
494 		HDERROR("unexpected response from server with missing user usage "
495 			"conditions data");
496 		return B_BAD_DATA;
497 	}
498 
499 	BMallocIO* copyMarkdownData = new BMallocIO();
500 	result = _RetrieveUserUsageConditionsCopy(metaDataCode, copyMarkdownData);
501 
502 	if (result != B_OK)
503 		return result;
504 
505 	conditions.SetCode(metaDataCode);
506 	conditions.SetMinimumAge(metaDataMinimumAge);
507 	conditions.SetCopyMarkdown(
508 		BString(static_cast<const char*>(copyMarkdownData->Buffer()),
509 			copyMarkdownData->BufferLength()));
510 
511 	return B_OK;
512 }
513 
514 
515 status_t
AgreeUserUsageConditions(const BString & code,BMessage & responsePayload)516 WebAppInterface::AgreeUserUsageConditions(const BString& code,
517 	BMessage& responsePayload)
518 {
519 	BMallocIO* requestEnvelopeData = new BMallocIO();
520 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
521 
522 	requestEnvelopeWriter.WriteObjectStart();
523 	requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
524 	requestEnvelopeWriter.WriteString(code.String());
525 	requestEnvelopeWriter.WriteObjectName("nickname");
526 	requestEnvelopeWriter.WriteString(Nickname());
527 	requestEnvelopeWriter.WriteObjectEnd();
528 
529 	// now fetch this information into an object.
530 
531 	return _SendJsonRequest("user/agree-user-usage-conditions",
532 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
533 		NEEDS_AUTHORIZATION, responsePayload);
534 }
535 
536 
537 status_t
_RetrieveUserUsageConditionsMeta(const BString & code,BMessage & message)538 WebAppInterface::_RetrieveUserUsageConditionsMeta(const BString& code,
539 	BMessage& message)
540 {
541 	BMallocIO* requestEnvelopeData = new BMallocIO();
542 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
543 
544 	requestEnvelopeWriter.WriteObjectStart();
545 
546 	if (!code.IsEmpty()) {
547 		requestEnvelopeWriter.WriteObjectName("code");
548 		requestEnvelopeWriter.WriteString(code.String());
549 	}
550 
551 	requestEnvelopeWriter.WriteObjectEnd();
552 
553 	// now fetch this information into an object.
554 
555 	return _SendJsonRequest("user/get-user-usage-conditions",
556 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
557 		0, message);
558 }
559 
560 
561 status_t
_RetrieveUserUsageConditionsCopy(const BString & code,BDataIO * stream)562 WebAppInterface::_RetrieveUserUsageConditionsCopy(const BString& code,
563 	BDataIO* stream)
564 {
565 	return _SendRawGetRequest(
566 		BString("/__user/usageconditions/") << code << "/document.md",
567 		stream);
568 }
569 
570 
571 status_t
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)572 WebAppInterface::CreateUserRating(const BString& packageName,
573 	const BPackageVersion& version,
574 	const BString& architecture,
575 	const BString& webAppRepositoryCode,
576 	const BString& webAppRepositorySourceCode,
577 	const BString& naturalLanguageCode,
578 		// This is the "ID" in the ICU system; the term `code` is used with the
579 		// server system.
580 	const BString& comment,
581 	const BString& stability, int rating, BMessage& message)
582 {
583 	BMallocIO* requestEnvelopeData = new BMallocIO();
584 		// BHttpRequest later takes ownership of this.
585 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
586 
587 	requestEnvelopeWriter.WriteObjectStart();
588 	requestEnvelopeWriter.WriteObjectName("pkgName");
589 	requestEnvelopeWriter.WriteString(packageName.String());
590 	requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
591 	requestEnvelopeWriter.WriteString(architecture.String());
592 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
593 	requestEnvelopeWriter.WriteString(webAppRepositoryCode.String());
594 	requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
595 	requestEnvelopeWriter.WriteString(webAppRepositorySourceCode.String());
596 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
597 	requestEnvelopeWriter.WriteString(naturalLanguageCode.String());
598 	requestEnvelopeWriter.WriteObjectName("pkgVersionType");
599 	requestEnvelopeWriter.WriteString("SPECIFIC");
600 	requestEnvelopeWriter.WriteObjectName("userNickname");
601 	requestEnvelopeWriter.WriteString(Nickname());
602 
603 	if (!version.Major().IsEmpty()) {
604 		requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
605 		requestEnvelopeWriter.WriteString(version.Major());
606 	}
607 
608 	if (!version.Minor().IsEmpty()) {
609 		requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
610 		requestEnvelopeWriter.WriteString(version.Minor());
611 	}
612 
613 	if (!version.Micro().IsEmpty()) {
614 		requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
615 		requestEnvelopeWriter.WriteString(version.Micro());
616 	}
617 
618 	if (!version.PreRelease().IsEmpty()) {
619 		requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
620 		requestEnvelopeWriter.WriteString(version.PreRelease());
621 	}
622 
623 	if (version.Revision() != 0) {
624 		requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
625 		requestEnvelopeWriter.WriteInteger(version.Revision());
626 	}
627 
628 	if (rating > 0.0f) {
629 		requestEnvelopeWriter.WriteObjectName("rating");
630     	requestEnvelopeWriter.WriteInteger(rating);
631 	}
632 
633 	if (stability.Length() > 0) {
634 		requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
635 		requestEnvelopeWriter.WriteString(stability);
636 	}
637 
638 	if (comment.Length() > 0) {
639 		requestEnvelopeWriter.WriteObjectName("comment");
640 		requestEnvelopeWriter.WriteString(comment.String());
641 	}
642 
643 	requestEnvelopeWriter.WriteObjectEnd();
644 
645 	return _SendJsonRequest("user-rating/create-user-rating",
646 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
647 		NEEDS_AUTHORIZATION, message);
648 }
649 
650 
651 status_t
UpdateUserRating(const BString & ratingID,const BString & naturalLanguageCode,const BString & comment,const BString & stability,int rating,bool active,BMessage & message)652 WebAppInterface::UpdateUserRating(const BString& ratingID,
653 	const BString& naturalLanguageCode,
654 		// This is the "ID" in the ICU system; the term `code` is used with the
655 		// server system.
656 	const BString& comment,
657 	const BString& stability, int rating, bool active, BMessage& message)
658 {
659 	BMallocIO* requestEnvelopeData = new BMallocIO();
660 		// BHttpRequest later takes ownership of this.
661 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
662 
663 	requestEnvelopeWriter.WriteObjectStart();
664 
665 	requestEnvelopeWriter.WriteObjectName("code");
666 	requestEnvelopeWriter.WriteString(ratingID.String());
667 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
668 	requestEnvelopeWriter.WriteString(naturalLanguageCode.String());
669 	requestEnvelopeWriter.WriteObjectName("active");
670 	requestEnvelopeWriter.WriteBoolean(active);
671 
672 	requestEnvelopeWriter.WriteObjectName("filter");
673 	requestEnvelopeWriter.WriteArrayStart();
674 	requestEnvelopeWriter.WriteString("ACTIVE");
675 	requestEnvelopeWriter.WriteString("NATURALLANGUAGE");
676 	requestEnvelopeWriter.WriteString("USERRATINGSTABILITY");
677 	requestEnvelopeWriter.WriteString("COMMENT");
678 	requestEnvelopeWriter.WriteString("RATING");
679 	requestEnvelopeWriter.WriteArrayEnd();
680 
681 	if (rating >= 0) {
682 		requestEnvelopeWriter.WriteObjectName("rating");
683 		requestEnvelopeWriter.WriteInteger(rating);
684 	}
685 
686 	if (stability.Length() > 0) {
687 		requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
688 		requestEnvelopeWriter.WriteString(stability);
689 	}
690 
691 	if (comment.Length() > 0) {
692 		requestEnvelopeWriter.WriteObjectName("comment");
693 		requestEnvelopeWriter.WriteString(comment);
694 	}
695 
696 	requestEnvelopeWriter.WriteObjectEnd();
697 
698 	return _SendJsonRequest("user-rating/update-user-rating",
699 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
700 		NEEDS_AUTHORIZATION, message);
701 }
702 
703 
704 /*! This method will call to the server to get a screenshot that will fit into
705     the specified width and height.
706 */
707 
708 status_t
RetrieveScreenshot(const BString & code,int32 width,int32 height,BDataIO * stream)709 WebAppInterface::RetrieveScreenshot(const BString& code,
710 	int32 width, int32 height, BDataIO* stream)
711 {
712 	return _SendRawGetRequest(
713 		BString("/__pkgscreenshot/") << code << ".png" << "?tw="
714 			<< width << "&th=" << height, stream);
715 }
716 
717 
718 status_t
RequestCaptcha(BMessage & message)719 WebAppInterface::RequestCaptcha(BMessage& message)
720 {
721 	BMallocIO* requestEnvelopeData = new BMallocIO();
722 		// BHttpRequest later takes ownership of this.
723 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
724 
725 	requestEnvelopeWriter.WriteObjectStart();
726 	requestEnvelopeWriter.WriteObjectEnd();
727 
728 	return _SendJsonRequest("captcha/generate-captcha",
729 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
730 		0, message);
731 }
732 
733 
734 status_t
CreateUser(const BString & nickName,const BString & passwordClear,const BString & email,const BString & captchaToken,const BString & captchaResponse,const BString & naturalLanguageCode,const BString & userUsageConditionsCode,BMessage & message)735 WebAppInterface::CreateUser(const BString& nickName,
736 	const BString& passwordClear,
737 	const BString& email,
738 	const BString& captchaToken,
739 	const BString& captchaResponse,
740 	const BString& naturalLanguageCode,
741 		// This is the "ID" in the ICU system; the term `code` is used with the
742 		// server system.
743 	const BString& userUsageConditionsCode,
744 	BMessage& message)
745 {
746 		// BHttpRequest later takes ownership of this.
747 	BMallocIO* requestEnvelopeData = new BMallocIO();
748 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
749 
750 	requestEnvelopeWriter.WriteObjectStart();
751 
752 	requestEnvelopeWriter.WriteObjectName("nickname");
753 	requestEnvelopeWriter.WriteString(nickName.String());
754 	requestEnvelopeWriter.WriteObjectName("passwordClear");
755 	requestEnvelopeWriter.WriteString(passwordClear.String());
756 	requestEnvelopeWriter.WriteObjectName("captchaToken");
757 	requestEnvelopeWriter.WriteString(captchaToken.String());
758 	requestEnvelopeWriter.WriteObjectName("captchaResponse");
759 	requestEnvelopeWriter.WriteString(captchaResponse.String());
760 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
761 	requestEnvelopeWriter.WriteString(naturalLanguageCode.String());
762 	requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
763 	requestEnvelopeWriter.WriteString(userUsageConditionsCode.String());
764 
765 	if (!email.IsEmpty()) {
766 		requestEnvelopeWriter.WriteObjectName("email");
767 		requestEnvelopeWriter.WriteString(email.String());
768 	}
769 
770 	requestEnvelopeWriter.WriteObjectEnd();
771 
772 	return _SendJsonRequest("user/create-user", requestEnvelopeData,
773 		_LengthAndSeekToZero(requestEnvelopeData), 0, message);
774 }
775 
776 
777 /*! This method will authenticate the user set in the credentials and will
778     retain the resultant access token for authenticating any latter API calls.
779 */
780 
781 status_t
AuthenticateUserRetainingAccessToken()782 WebAppInterface::AuthenticateUserRetainingAccessToken()
783 {
784 	UserCredentials userCredentials = _Credentials();
785 
786 	if (!userCredentials.IsValid()) {
787 		HDINFO("unable to get a new access token as there are no credentials");
788 		return B_NOT_INITIALIZED;
789 	}
790 
791 	return _AuthenticateUserRetainingAccessToken(userCredentials.Nickname(),
792 		userCredentials.PasswordClear());
793 }
794 
795 
796 status_t
_AuthenticateUserRetainingAccessToken(const BString & nickName,const BString & passwordClear)797 WebAppInterface::_AuthenticateUserRetainingAccessToken(const BString& nickName,
798 	const BString& passwordClear) {
799 	AutoLocker<BLocker> lock(&fLock);
800 
801 	fAccessToken.Clear();
802 
803 	BMessage responseEnvelopeMessage;
804 	status_t result = AuthenticateUser(nickName, passwordClear, responseEnvelopeMessage);
805 
806 	AccessToken accessToken;
807 
808 	if (result == B_OK)
809 		result = UnpackAccessToken(responseEnvelopeMessage, accessToken);
810 
811 	if (result == B_OK)
812 		fAccessToken = accessToken;
813 
814 	return result;
815 }
816 
817 
818 status_t
AuthenticateUser(const BString & nickName,const BString & passwordClear,BMessage & message)819 WebAppInterface::AuthenticateUser(const BString& nickName,
820 	const BString& passwordClear, BMessage& message)
821 {
822 	BMallocIO* requestEnvelopeData = new BMallocIO();
823 		// BHttpRequest later takes ownership of this.
824 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
825 
826 	requestEnvelopeWriter.WriteObjectStart();
827 
828 	requestEnvelopeWriter.WriteObjectName("nickname");
829 	requestEnvelopeWriter.WriteString(nickName.String());
830 	requestEnvelopeWriter.WriteObjectName("passwordClear");
831 	requestEnvelopeWriter.WriteString(passwordClear.String());
832 
833 	requestEnvelopeWriter.WriteObjectEnd();
834 
835 	return _SendJsonRequest("user/authenticate-user",
836 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
837 		0, message);
838 }
839 
840 
841 status_t
IncrementViewCounter(const PackageInfoRef package,const DepotInfoRef depot,BMessage & message)842 WebAppInterface::IncrementViewCounter(const PackageInfoRef package,
843 	const DepotInfoRef depot, BMessage& message)
844 {
845 	BMallocIO* requestEnvelopeData = new BMallocIO();
846 		// BHttpRequest later takes ownership of this.
847 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
848 
849 	requestEnvelopeWriter.WriteObjectStart();
850 
851 	requestEnvelopeWriter.WriteObjectName("architectureCode");
852 	requestEnvelopeWriter.WriteString(package->Architecture());
853 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
854 	requestEnvelopeWriter.WriteString(depot->WebAppRepositoryCode());
855 	requestEnvelopeWriter.WriteObjectName("repositorySourceCode");
856 	requestEnvelopeWriter.WriteString(depot->WebAppRepositorySourceCode());
857 	requestEnvelopeWriter.WriteObjectName("name");
858 	requestEnvelopeWriter.WriteString(package->Name());
859 
860 	const BPackageVersion version = package->Version();
861 	if (!version.Major().IsEmpty()) {
862 		requestEnvelopeWriter.WriteObjectName("major");
863 		requestEnvelopeWriter.WriteString(version.Major());
864 	}
865 	if (!version.Minor().IsEmpty()) {
866 		requestEnvelopeWriter.WriteObjectName("minor");
867 		requestEnvelopeWriter.WriteString(version.Minor());
868 	}
869 	if (!version.Micro().IsEmpty()) {
870 		requestEnvelopeWriter.WriteObjectName("micro");
871 		requestEnvelopeWriter.WriteString(version.Micro());
872 	}
873 	if (!version.PreRelease().IsEmpty()) {
874 		requestEnvelopeWriter.WriteObjectName("preRelease");
875 		requestEnvelopeWriter.WriteString(version.PreRelease());
876 	}
877 	if (version.Revision() != 0) {
878 		requestEnvelopeWriter.WriteObjectName("revision");
879 		requestEnvelopeWriter.WriteInteger(
880 			static_cast<int64>(version.Revision()));
881 	}
882 
883 	requestEnvelopeWriter.WriteObjectEnd();
884 
885 	return _SendJsonRequest("pkg/increment-view-counter",
886 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
887 		0, message);
888 }
889 
890 
891 status_t
RetrievePasswordRequirements(PasswordRequirements & passwordRequirements)892 WebAppInterface::RetrievePasswordRequirements(
893 	PasswordRequirements& passwordRequirements)
894 {
895 	BMessage responseEnvelopeMessage;
896 	status_t result = _RetrievePasswordRequirementsMeta(
897 		responseEnvelopeMessage);
898 
899 	if (result != B_OK)
900 		return result;
901 
902 	BMessage resultMessage;
903 
904 	result = responseEnvelopeMessage.FindMessage("result", &resultMessage);
905 
906 	if (result != B_OK) {
907 		HDERROR("bad response envelope missing 'result' entry");
908 		return result;
909 	}
910 
911 	double value;
912 
913 	if (resultMessage.FindDouble("minPasswordLength", &value) == B_OK)
914 		passwordRequirements.SetMinPasswordLength((uint32) value);
915 
916 	if (resultMessage.FindDouble("minPasswordUppercaseChar", &value) == B_OK)
917 		passwordRequirements.SetMinPasswordUppercaseChar((uint32) value);
918 
919 	if (resultMessage.FindDouble("minPasswordDigitsChar", &value) == B_OK)
920 		passwordRequirements.SetMinPasswordDigitsChar((uint32) value);
921 
922 	return result;
923 }
924 
925 
926 status_t
_RetrievePasswordRequirementsMeta(BMessage & message)927 WebAppInterface::_RetrievePasswordRequirementsMeta(BMessage& message)
928 {
929 	BMallocIO* requestEnvelopeData = new BMallocIO();
930 		// BHttpRequest later takes ownership of this.
931 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
932 
933 	requestEnvelopeWriter.WriteObjectStart();
934 	requestEnvelopeWriter.WriteObjectEnd();
935 
936 	return _SendJsonRequest("user/get-password-requirements",
937 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
938 		0, message);
939 }
940 
941 
942 /*!	JSON-RPC invocations return a response.  The response may be either
943 	a result or it may be an error depending on the response structure.
944 	If it is an error then there may be additional detail that is the
945 	error code and message.  This method will extract the error code
946 	from the response.  This method will return 0 if the payload does
947 	not look like an error.
948 */
949 
950 /*static*/ int32
ErrorCodeFromResponse(BMessage & responseEnvelopeMessage)951 WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
952 {
953 	BMessage error;
954 	double code;
955 
956 	if (responseEnvelopeMessage.FindMessage("error", &error) == B_OK
957 		&& error.FindDouble("code", &code) == B_OK) {
958 		return (int32) code;
959 	}
960 
961 	return 0;
962 }
963 
964 
965 // #pragma mark - private
966 
967 
968 status_t
_SendJsonRequest(const char * urlPathComponents,BPositionIO * requestData,size_t requestDataSize,uint32 flags,BMessage & reply)969 WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
970 	BPositionIO* requestData, size_t requestDataSize, uint32 flags,
971 	BMessage& reply)
972 {
973 	bool needsAuthorization = (flags & NEEDS_AUTHORIZATION) != 0;
974 	AccessToken accessToken;
975 
976 	if (needsAuthorization)
977 		accessToken = _ObtainValidAccessToken();
978 
979 	return _SendJsonRequest(urlPathComponents, accessToken, requestData,
980 		requestDataSize, flags, reply);
981 }
982 
983 
984 /*static*/ status_t
_SendJsonRequest(const char * urlPathComponents,const AccessToken & accessToken,BPositionIO * requestData,size_t requestDataSize,uint32 flags,BMessage & reply)985 WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
986 	const AccessToken& accessToken, BPositionIO* requestData,
987 	size_t requestDataSize, uint32 flags, BMessage& reply)
988 {
989 	if (requestDataSize == 0) {
990 		HDINFO("%s; empty request payload", PROTOCOL_NAME);
991 		return B_ERROR;
992 	}
993 
994 	if (!ServerHelper::IsNetworkAvailable()) {
995 		HDDEBUG("%s; dropping request to ...[%s] as network is not"
996 			" available", PROTOCOL_NAME, urlPathComponents);
997 		delete requestData;
998 		return HD_NETWORK_INACCESSIBLE;
999 	}
1000 
1001 	if (ServerSettings::IsClientTooOld()) {
1002 		HDDEBUG("%s; dropping request to ...[%s] as client is too old",
1003 			PROTOCOL_NAME, urlPathComponents);
1004 		delete requestData;
1005 		return HD_CLIENT_TOO_OLD;
1006 	}
1007 
1008 	bool needsAuthorization = (flags & NEEDS_AUTHORIZATION) != 0;
1009 
1010 	if (needsAuthorization && !accessToken.IsValid()) {
1011 		HDDEBUG("%s; dropping request to ...[%s] as access token is not valid",
1012 			PROTOCOL_NAME, urlPathComponents);
1013 		delete requestData;
1014 		return B_NOT_ALLOWED;
1015 	}
1016 
1017 	BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v2/")
1018 		<< urlPathComponents);
1019 	HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME,
1020 		url.UrlString().String());
1021 
1022 	// If the request payload is logged then it must be copied to local memory
1023 	// from the stream.  This then requires that the request data is then
1024 	// delivered from memory.
1025 
1026 	if (Logger::IsTraceEnabled()) {
1027 		HDLOGPREFIX(LOG_LEVEL_TRACE)
1028 		printf("%s request; ", PROTOCOL_NAME);
1029 		_LogPayload(requestData, requestDataSize);
1030 		printf("\n");
1031 	}
1032 
1033 	ProtocolListener listener;
1034 	BUrlContext context;
1035 
1036 	BHttpHeaders headers;
1037 	headers.AddHeader("Content-Type", "application/json");
1038 	headers.AddHeader("Accept", "application/json");
1039 	ServerSettings::AugmentHeaders(headers);
1040 
1041 	BHttpRequest* request = make_http_request(url, NULL, &listener, &context);
1042 	ObjectDeleter<BHttpRequest> _(request);
1043 	if (request == NULL)
1044 		return B_ERROR;
1045 	request->SetMethod(B_HTTP_POST);
1046 	request->SetHeaders(headers);
1047 
1048 	if (needsAuthorization) {
1049 		BHttpAuthentication authentication;
1050 		authentication.SetMethod(B_HTTP_AUTHENTICATION_BEARER);
1051 		authentication.SetToken(accessToken.Token());
1052 		context.AddAuthentication(url, authentication);
1053 	}
1054 
1055 	request->AdoptInputData(requestData, requestDataSize);
1056 
1057 	BMallocIO replyData;
1058 	request->SetOutput(&replyData);
1059 
1060 	thread_id thread = request->Run();
1061 	wait_for_thread(thread, NULL);
1062 
1063 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
1064 		request->Result());
1065 
1066 	int32 statusCode = result.StatusCode();
1067 
1068 	HDDEBUG("%s; did receive http-status [%" B_PRId32 "] from [%s]",
1069 		PROTOCOL_NAME, statusCode, url.UrlString().String());
1070 
1071 	switch (statusCode) {
1072 		case B_HTTP_STATUS_OK:
1073 			break;
1074 
1075 		case B_HTTP_STATUS_PRECONDITION_FAILED:
1076 			ServerHelper::NotifyClientTooOld(result.Headers());
1077 			return HD_CLIENT_TOO_OLD;
1078 
1079 		default:
1080 			HDERROR("%s; request to endpoint [.../%s] failed with http "
1081 				"status [%" B_PRId32 "]\n", PROTOCOL_NAME, urlPathComponents,
1082 				statusCode);
1083 			return B_ERROR;
1084 	}
1085 
1086 	replyData.Seek(0, SEEK_SET);
1087 
1088 	if (Logger::IsTraceEnabled()) {
1089 		HDLOGPREFIX(LOG_LEVEL_TRACE)
1090 		printf("%s; response; ", PROTOCOL_NAME);
1091 		_LogPayload(&replyData, replyData.BufferLength());
1092 		printf("\n");
1093 	}
1094 
1095 	BJsonMessageWriter jsonMessageWriter(reply);
1096 	BJson::Parse(&replyData, &jsonMessageWriter);
1097 	status_t status = jsonMessageWriter.ErrorStatus();
1098 
1099 	if (Logger::IsTraceEnabled() && status == B_BAD_DATA) {
1100 		BString resultString(static_cast<const char *>(replyData.Buffer()),
1101 			replyData.BufferLength());
1102 		HDERROR("Parser choked on JSON:\n%s", resultString.String());
1103 	}
1104 	return status;
1105 }
1106 
1107 
1108 status_t
_SendJsonRequest(const char * urlPathComponents,const BString & jsonString,uint32 flags,BMessage & reply)1109 WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
1110 	const BString& jsonString, uint32 flags, BMessage& reply)
1111 {
1112 	// gets 'adopted' by the subsequent http request.
1113 	BMemoryIO* data = new BMemoryIO(jsonString.String(),
1114 		jsonString.Length() - 1);
1115 
1116 	return _SendJsonRequest(urlPathComponents, data, jsonString.Length() - 1,
1117 		flags, reply);
1118 }
1119 
1120 
1121 status_t
_SendRawGetRequest(const BString urlPathComponents,BDataIO * stream)1122 WebAppInterface::_SendRawGetRequest(const BString urlPathComponents,
1123 	BDataIO* stream)
1124 {
1125 	BUrl url = ServerSettings::CreateFullUrl(urlPathComponents);
1126 
1127 	HDDEBUG("http-get; will make request to [%s]",
1128 		url.UrlString().String());
1129 
1130 	ProtocolListener listener;
1131 
1132 	BHttpHeaders headers;
1133 	ServerSettings::AugmentHeaders(headers);
1134 
1135 	BHttpRequest *request = make_http_request(url, stream, &listener);
1136 	ObjectDeleter<BHttpRequest> _(request);
1137 	if (request == NULL)
1138 		return B_ERROR;
1139 	request->SetMethod(B_HTTP_GET);
1140 	request->SetHeaders(headers);
1141 
1142 	thread_id thread = request->Run();
1143 	wait_for_thread(thread, NULL);
1144 
1145 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
1146 		request->Result());
1147 
1148 	int32 statusCode = result.StatusCode();
1149 
1150 	HDDEBUG("http-get; did receive http-status [%" B_PRId32 "] from [%s]",
1151 		statusCode, url.UrlString().String());
1152 
1153 	if (statusCode == 200)
1154 		return B_OK;
1155 
1156 	HDERROR("failed to get data from '%s': %" B_PRIi32 "",
1157 		url.UrlString().String(), statusCode);
1158 	return B_ERROR;
1159 }
1160 
1161 
1162 void
_LogPayload(BPositionIO * requestData,size_t size)1163 WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size)
1164 {
1165 	off_t requestDataOffset = requestData->Position();
1166 	char buffer[LOG_PAYLOAD_LIMIT];
1167 
1168 	if (size > LOG_PAYLOAD_LIMIT)
1169 		size = LOG_PAYLOAD_LIMIT;
1170 
1171 	if (B_OK != requestData->ReadExactly(buffer, size)) {
1172 		printf("%s; error logging payload", PROTOCOL_NAME);
1173 	} else {
1174 		for (uint32 i = 0; i < size; i++) {
1175     		bool esc = buffer[i] > 126 ||
1176     			(buffer[i] < 0x20 && buffer[i] != 0x0a);
1177 
1178     		if (esc)
1179     			printf("\\u%02x", buffer[i]);
1180     		else
1181     			putchar(buffer[i]);
1182     	}
1183 
1184     	if (size == LOG_PAYLOAD_LIMIT)
1185     		printf("...(continues)");
1186 	}
1187 
1188 	requestData->Seek(requestDataOffset, SEEK_SET);
1189 }
1190 
1191 
1192 /*!	This will get the position of the data to get the length an then sets the
1193 	offset to zero so that it can be re-read for reading the payload in to log
1194 	or send.
1195 */
1196 
1197 off_t
_LengthAndSeekToZero(BPositionIO * data)1198 WebAppInterface::_LengthAndSeekToZero(BPositionIO* data)
1199 {
1200 	off_t dataSize = data->Position();
1201     data->Seek(0, SEEK_SET);
1202     return dataSize;
1203 }
1204 
1205 
1206 UserCredentials
_Credentials()1207 WebAppInterface::_Credentials()
1208 {
1209 	AutoLocker<BLocker> lock(&fLock);
1210 	return fCredentials;
1211 }
1212 
1213 
1214 AccessToken
_ObtainValidAccessToken()1215 WebAppInterface::_ObtainValidAccessToken()
1216 {
1217 	AutoLocker<BLocker> lock(&fLock);
1218 
1219 	uint64 now = static_cast<uint64>(time(NULL)) * 1000;
1220 
1221 	if (!fAccessToken.IsValid(now)) {
1222 		HDINFO("clearing cached access token as it is no longer valid");
1223 		fAccessToken.Clear();
1224 	}
1225 
1226 	if (!fAccessToken.IsValid()) {
1227 		HDINFO("no cached access token present; will obtain a new one");
1228 		AuthenticateUserRetainingAccessToken();
1229 	}
1230 
1231 	return fAccessToken;
1232 }
1233