xref: /haiku/src/apps/haikudepot/server/WebAppInterface.cpp (revision 02354704729d38c3b078c696adc1bbbd33cbcf72)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2016-2021, Andrew Lindesay <apl@lindesay.co.nz>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 #include "WebAppInterface.h"
8 
9 #include <AutoDeleter.h>
10 #include <Application.h>
11 #include <HttpHeaders.h>
12 #include <HttpRequest.h>
13 #include <Json.h>
14 #include <JsonTextWriter.h>
15 #include <JsonMessageWriter.h>
16 #include <Message.h>
17 #include <Url.h>
18 #include <UrlContext.h>
19 #include <UrlProtocolListener.h>
20 #include <UrlProtocolRoster.h>
21 
22 #include "DataIOUtils.h"
23 #include "HaikuDepotConstants.h"
24 #include "Logger.h"
25 #include "ServerSettings.h"
26 #include "ServerHelper.h"
27 
28 
29 using namespace BPrivate::Network;
30 
31 
32 #define BASEURL_DEFAULT "https://depot.haiku-os.org"
33 #define USERAGENT_FALLBACK_VERSION "0.0.0"
34 #define PROTOCOL_NAME "post-json"
35 #define LOG_PAYLOAD_LIMIT 8192
36 
37 
38 class ProtocolListener : public BUrlProtocolListener {
39 public:
40 	ProtocolListener()
41 	{
42 	}
43 
44 	virtual ~ProtocolListener()
45 	{
46 	}
47 
48 	virtual	void ConnectionOpened(BUrlRequest* caller)
49 	{
50 	}
51 
52 	virtual void HostnameResolved(BUrlRequest* caller, const char* ip)
53 	{
54 	}
55 
56 	virtual void ResponseStarted(BUrlRequest* caller)
57 	{
58 	}
59 
60 	virtual void HeadersReceived(BUrlRequest* caller)
61 	{
62 	}
63 
64 	virtual void BytesWritten(BUrlRequest* caller, size_t bytesWritten)
65 	{
66 	}
67 
68 	virtual	void DownloadProgress(BUrlRequest* caller, off_t bytesReceived,
69 		ssize_t bytesTotal)
70 	{
71 	}
72 
73 	virtual void UploadProgress(BUrlRequest* caller, off_t bytesSent,
74 		ssize_t bytesTotal)
75 	{
76 	}
77 
78 	virtual void RequestCompleted(BUrlRequest* caller, bool success)
79 	{
80 	}
81 
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*
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 int
107 WebAppInterface::fRequestIndex = 0;
108 
109 
110 enum {
111 	NEEDS_AUTHORIZATION = 1 << 0,
112 };
113 
114 
115 WebAppInterface::WebAppInterface()
116 {
117 }
118 
119 
120 WebAppInterface::WebAppInterface(const WebAppInterface& other)
121 	:
122 	fCredentials(other.fCredentials)
123 {
124 }
125 
126 
127 WebAppInterface::~WebAppInterface()
128 {
129 }
130 
131 
132 WebAppInterface&
133 WebAppInterface::operator=(const WebAppInterface& other)
134 {
135 	if (this == &other)
136 		return *this;
137 	fCredentials = other.fCredentials;
138 	return *this;
139 }
140 
141 
142 void
143 WebAppInterface::SetAuthorization(const UserCredentials& value)
144 {
145 	fCredentials = value;
146 }
147 
148 
149 const BString&
150 WebAppInterface::Nickname() const
151 {
152 	return fCredentials.Nickname();
153 }
154 
155 
156 status_t
157 WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
158 {
159 	BMallocIO* requestEnvelopeData = new BMallocIO();
160 		// BHttpRequest later takes ownership of this.
161 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
162 
163 	requestEnvelopeWriter.WriteObjectStart();
164 	requestEnvelopeWriter.WriteObjectName("pkgName");
165 	requestEnvelopeWriter.WriteString(packageName.String());
166 	requestEnvelopeWriter.WriteObjectEnd();
167 
168 	return _SendJsonRequest("pkg/get-pkg-change-log",
169 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
170 		0, message);
171 }
172 
173 
174 status_t
175 WebAppInterface::RetreiveUserRatingsForPackageForDisplay(
176 	const BString& packageName, const BString& webAppRepositoryCode,
177 	int resultOffset, int maxResults, BMessage& message)
178 {
179 		// BHttpRequest later takes ownership of this.
180 	BMallocIO* requestEnvelopeData = new BMallocIO();
181 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
182 
183 	requestEnvelopeWriter.WriteObjectStart();
184 	requestEnvelopeWriter.WriteObjectName("pkgName");
185 	requestEnvelopeWriter.WriteString(packageName.String());
186 	requestEnvelopeWriter.WriteObjectName("offset");
187 	requestEnvelopeWriter.WriteInteger(resultOffset);
188 	requestEnvelopeWriter.WriteObjectName("limit");
189 	requestEnvelopeWriter.WriteInteger(maxResults);
190 
191 	if (!webAppRepositoryCode.IsEmpty()) {
192 		requestEnvelopeWriter.WriteObjectName("repositoryCode");
193 		requestEnvelopeWriter.WriteString(webAppRepositoryCode);
194 	}
195 
196 	requestEnvelopeWriter.WriteObjectEnd();
197 
198 	return _SendJsonRequest("user-rating/search-user-ratings",
199 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
200 		0, message);
201 }
202 
203 
204 status_t
205 WebAppInterface::RetreiveUserRatingForPackageAndVersionByUser(
206 	const BString& packageName, const BPackageVersion& version,
207 	const BString& architecture, const BString &repositoryCode,
208 	const BString& userNickname, BMessage& message)
209 {
210 		// BHttpRequest later takes ownership of this.
211 	BMallocIO* requestEnvelopeData = new BMallocIO();
212 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
213 
214 	requestEnvelopeWriter.WriteObjectStart();
215 
216 	requestEnvelopeWriter.WriteObjectName("userNickname");
217 	requestEnvelopeWriter.WriteString(userNickname.String());
218 	requestEnvelopeWriter.WriteObjectName("pkgName");
219 	requestEnvelopeWriter.WriteString(packageName.String());
220 	requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
221 	requestEnvelopeWriter.WriteString(architecture.String());
222 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
223 	requestEnvelopeWriter.WriteString(repositoryCode.String());
224 
225 	if (version.Major().Length() > 0) {
226 		requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
227 		requestEnvelopeWriter.WriteString(version.Major().String());
228 	}
229 
230 	if (version.Minor().Length() > 0) {
231 		requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
232 		requestEnvelopeWriter.WriteString(version.Minor().String());
233 	}
234 
235 	if (version.Micro().Length() > 0) {
236 		requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
237 		requestEnvelopeWriter.WriteString(version.Micro().String());
238 	}
239 
240 	if (version.PreRelease().Length() > 0) {
241 		requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
242 		requestEnvelopeWriter.WriteString(version.PreRelease().String());
243 	}
244 
245 	if (version.Revision() != 0) {
246 		requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
247 		requestEnvelopeWriter.WriteInteger(version.Revision());
248 	}
249 
250 	requestEnvelopeWriter.WriteObjectEnd();
251 
252 	return _SendJsonRequest(
253 		"user-rating/get-user-rating-by-user-and-pkg-version",
254 		requestEnvelopeData,
255 		_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
256 		message);
257 }
258 
259 
260 /*!	This method will fill out the supplied UserDetail object with information
261 	about the user that is supplied in the credentials.  Importantly it will
262 	also authenticate the request with the details of the credentials and will
263 	not use the credentials that are configured in 'fCredentials'.
264 */
265 
266 status_t
267 WebAppInterface::RetrieveUserDetailForCredentials(
268 	const UserCredentials& credentials, BMessage& message)
269 {
270 	if (!credentials.IsValid()) {
271 		debugger("the credentials supplied are invalid so it is not possible "
272 			"to obtain the user detail");
273 	}
274 
275 		// BHttpRequest later takes ownership of this.
276 	BMallocIO* requestEnvelopeData = new BMallocIO();
277 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
278 
279 	requestEnvelopeWriter.WriteObjectStart();
280 	requestEnvelopeWriter.WriteObjectName("nickname");
281 	requestEnvelopeWriter.WriteString(credentials.Nickname().String());
282 	requestEnvelopeWriter.WriteObjectEnd();
283 
284 	status_t result = _SendJsonRequest("user/get-user", credentials,
285 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
286 		NEEDS_AUTHORIZATION, message);
287 		// note that the credentials used here are passed in as args.
288 
289 	return result;
290 }
291 
292 
293 /*!	This method will return the credentials for the currently authenticated
294 	user.
295 */
296 
297 status_t
298 WebAppInterface::RetrieveCurrentUserDetail(BMessage& message)
299 {
300 	return RetrieveUserDetailForCredentials(fCredentials, message);
301 }
302 
303 
304 /*!	When the user requests user detail, the server sends back an envelope of
305 	response data.  This method will unpack the data into a model object.
306 	\return Not B_OK if something went wrong.
307 */
308 
309 /*static*/ status_t
310 WebAppInterface::UnpackUserDetail(BMessage& responseEnvelopeMessage,
311 	UserDetail& userDetail)
312 {
313 	BMessage resultMessage;
314 	status_t result = responseEnvelopeMessage.FindMessage(
315 		"result", &resultMessage);
316 
317 	if (result != B_OK) {
318 		HDERROR("bad response envelope missing 'result' entry");
319 		return result;
320 	}
321 
322 	BString nickname;
323 	result = resultMessage.FindString("nickname", &nickname);
324 	userDetail.SetNickname(nickname);
325 
326 	BMessage agreementMessage;
327 	if (resultMessage.FindMessage("userUsageConditionsAgreement",
328 		&agreementMessage) == B_OK) {
329 		BString code;
330 		BDateTime agreedToTimestamp;
331 		BString userUsageConditionsCode;
332 		UserUsageConditionsAgreement agreement = userDetail.Agreement();
333 		bool isLatest;
334 
335 		if (agreementMessage.FindString("userUsageConditionsCode",
336 			&userUsageConditionsCode) == B_OK) {
337 			agreement.SetCode(userUsageConditionsCode);
338 		}
339 
340 		double timestampAgreedMillis;
341 		if (agreementMessage.FindDouble("timestampAgreed",
342 			&timestampAgreedMillis) == B_OK) {
343 			agreement.SetTimestampAgreed((uint64) timestampAgreedMillis);
344 		}
345 
346 		if (agreementMessage.FindBool("isLatest", &isLatest)
347 			== B_OK) {
348 			agreement.SetIsLatest(isLatest);
349 		}
350 
351 		userDetail.SetAgreement(agreement);
352 	}
353 
354 	return result;
355 }
356 
357 
358 /*!	\brief Returns data relating to the user usage conditions
359 
360 	\param code defines the version of the data to return or if empty then the
361 		latest is returned.
362 
363 	This method will go to the server and get details relating to the user usage
364 	conditions.  It does this in two API calls; first gets the details (the
365 	minimum age) and in the second call, the text of the conditions is returned.
366 */
367 
368 status_t
369 WebAppInterface::RetrieveUserUsageConditions(const BString& code,
370 	UserUsageConditions& conditions)
371 {
372 	BMessage responseEnvelopeMessage;
373 	status_t result = _RetrieveUserUsageConditionsMeta(code,
374 		responseEnvelopeMessage);
375 
376 	if (result != B_OK)
377 		return result;
378 
379 	BMessage resultMessage;
380 	if (responseEnvelopeMessage.FindMessage("result", &resultMessage) != B_OK) {
381 		HDERROR("bad response envelope missing 'result' entry");
382 		return B_BAD_DATA;
383 	}
384 
385 	BString metaDataCode;
386 	double metaDataMinimumAge;
387 	BString copyMarkdown;
388 
389 	if ( (resultMessage.FindString("code", &metaDataCode) != B_OK)
390 			|| (resultMessage.FindDouble(
391 				"minimumAge", &metaDataMinimumAge) != B_OK) ) {
392 		HDERROR("unexpected response from server with missing user usage "
393 			"conditions data");
394 		return B_BAD_DATA;
395 	}
396 
397 	BMallocIO* copyMarkdownData = new BMallocIO();
398 	result = _RetrieveUserUsageConditionsCopy(metaDataCode, copyMarkdownData);
399 
400 	if (result != B_OK)
401 		return result;
402 
403 	conditions.SetCode(metaDataCode);
404 	conditions.SetMinimumAge(metaDataMinimumAge);
405 	conditions.SetCopyMarkdown(
406 		BString(static_cast<const char*>(copyMarkdownData->Buffer()),
407 			copyMarkdownData->BufferLength()));
408 
409 	return B_OK;
410 }
411 
412 
413 status_t
414 WebAppInterface::AgreeUserUsageConditions(const BString& code,
415 	BMessage& responsePayload)
416 {
417 	BMallocIO* requestEnvelopeData = new BMallocIO();
418 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
419 
420 	requestEnvelopeWriter.WriteObjectStart();
421 	requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
422 	requestEnvelopeWriter.WriteString(code.String());
423 	requestEnvelopeWriter.WriteObjectName("nickname");
424 	requestEnvelopeWriter.WriteString(fCredentials.Nickname());
425 	requestEnvelopeWriter.WriteObjectEnd();
426 
427 	// now fetch this information into an object.
428 
429 	return _SendJsonRequest("user/agree-user-usage-conditions",
430 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
431 		NEEDS_AUTHORIZATION, responsePayload);
432 }
433 
434 
435 status_t
436 WebAppInterface::_RetrieveUserUsageConditionsMeta(const BString& code,
437 	BMessage& message)
438 {
439 	BMallocIO* requestEnvelopeData = new BMallocIO();
440 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
441 
442 	requestEnvelopeWriter.WriteObjectStart();
443 
444 	if (!code.IsEmpty()) {
445 		requestEnvelopeWriter.WriteObjectName("code");
446 		requestEnvelopeWriter.WriteString(code.String());
447 	}
448 
449 	requestEnvelopeWriter.WriteObjectEnd();
450 
451 	// now fetch this information into an object.
452 
453 	return _SendJsonRequest("user/get-user-usage-conditions",
454 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
455 		0, message);
456 }
457 
458 
459 status_t
460 WebAppInterface::_RetrieveUserUsageConditionsCopy(const BString& code,
461 	BDataIO* stream)
462 {
463 	return _SendRawGetRequest(
464 		BString("/__user/usageconditions/") << code << "/document.md",
465 		stream);
466 }
467 
468 
469 status_t
470 WebAppInterface::CreateUserRating(const BString& packageName,
471 	const BPackageVersion& version,
472 	const BString& architecture, const BString& repositoryCode,
473 	const BString& languageCode, const BString& comment,
474 	const BString& stability, int rating, BMessage& message)
475 {
476 	BMallocIO* requestEnvelopeData = new BMallocIO();
477 		// BHttpRequest later takes ownership of this.
478 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
479 
480 	requestEnvelopeWriter.WriteObjectStart();
481 	requestEnvelopeWriter.WriteObjectName("pkgName");
482 	requestEnvelopeWriter.WriteString(packageName.String());
483 	requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
484 	requestEnvelopeWriter.WriteString(architecture.String());
485 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
486 	requestEnvelopeWriter.WriteString(repositoryCode.String());
487 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
488 	requestEnvelopeWriter.WriteString(languageCode.String());
489 	requestEnvelopeWriter.WriteObjectName("pkgVersionType");
490 	requestEnvelopeWriter.WriteString("SPECIFIC");
491 	requestEnvelopeWriter.WriteObjectName("userNickname");
492 	requestEnvelopeWriter.WriteString(fCredentials.Nickname());
493 
494 	if (!version.Major().IsEmpty()) {
495 		requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
496 		requestEnvelopeWriter.WriteString(version.Major());
497 	}
498 
499 	if (!version.Minor().IsEmpty()) {
500 		requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
501 		requestEnvelopeWriter.WriteString(version.Minor());
502 	}
503 
504 	if (!version.Micro().IsEmpty()) {
505 		requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
506 		requestEnvelopeWriter.WriteString(version.Micro());
507 	}
508 
509 	if (!version.PreRelease().IsEmpty()) {
510 		requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
511 		requestEnvelopeWriter.WriteString(version.PreRelease());
512 	}
513 
514 	if (version.Revision() != 0) {
515 		requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
516 		requestEnvelopeWriter.WriteInteger(version.Revision());
517 	}
518 
519 	if (rating > 0.0f) {
520 		requestEnvelopeWriter.WriteObjectName("rating");
521     	requestEnvelopeWriter.WriteInteger(rating);
522 	}
523 
524 	if (stability.Length() > 0) {
525 		requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
526 		requestEnvelopeWriter.WriteString(stability);
527 	}
528 
529 	if (comment.Length() > 0) {
530 		requestEnvelopeWriter.WriteObjectName("comment");
531 		requestEnvelopeWriter.WriteString(comment.String());
532 	}
533 
534 	requestEnvelopeWriter.WriteObjectEnd();
535 
536 	return _SendJsonRequest("user-rating/create-user-rating",
537 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
538 		NEEDS_AUTHORIZATION, message);
539 }
540 
541 
542 status_t
543 WebAppInterface::UpdateUserRating(const BString& ratingID,
544 	const BString& languageCode, const BString& comment,
545 	const BString& stability, int rating, bool active, BMessage& message)
546 {
547 	BMallocIO* requestEnvelopeData = new BMallocIO();
548 		// BHttpRequest later takes ownership of this.
549 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
550 
551 	requestEnvelopeWriter.WriteObjectStart();
552 
553 	requestEnvelopeWriter.WriteObjectName("code");
554 	requestEnvelopeWriter.WriteString(ratingID.String());
555 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
556 	requestEnvelopeWriter.WriteString(languageCode.String());
557 	requestEnvelopeWriter.WriteObjectName("active");
558 	requestEnvelopeWriter.WriteBoolean(active);
559 
560 	requestEnvelopeWriter.WriteObjectName("filter");
561 	requestEnvelopeWriter.WriteArrayStart();
562 	requestEnvelopeWriter.WriteString("ACTIVE");
563 	requestEnvelopeWriter.WriteString("NATURALLANGUAGE");
564 	requestEnvelopeWriter.WriteString("USERRATINGSTABILITY");
565 	requestEnvelopeWriter.WriteString("COMMENT");
566 	requestEnvelopeWriter.WriteString("RATING");
567 	requestEnvelopeWriter.WriteArrayEnd();
568 
569 	if (rating >= 0) {
570 		requestEnvelopeWriter.WriteObjectName("rating");
571 		requestEnvelopeWriter.WriteInteger(rating);
572 	}
573 
574 	if (stability.Length() > 0) {
575 		requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
576 		requestEnvelopeWriter.WriteString(stability);
577 	}
578 
579 	if (comment.Length() > 0) {
580 		requestEnvelopeWriter.WriteObjectName("comment");
581 		requestEnvelopeWriter.WriteString(comment);
582 	}
583 
584 	requestEnvelopeWriter.WriteObjectEnd();
585 
586 	return _SendJsonRequest("user-rating/update-user-rating",
587 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
588 		NEEDS_AUTHORIZATION, message);
589 }
590 
591 
592 status_t
593 WebAppInterface::RetrieveScreenshot(const BString& code,
594 	int32 width, int32 height, BDataIO* stream)
595 {
596 	return _SendRawGetRequest(
597 		BString("/__pkgscreenshot/") << code << ".png" << "?tw="
598 			<< width << "&th=" << height, stream);
599 }
600 
601 
602 status_t
603 WebAppInterface::RequestCaptcha(BMessage& message)
604 {
605 	BMallocIO* requestEnvelopeData = new BMallocIO();
606 		// BHttpRequest later takes ownership of this.
607 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
608 
609 	requestEnvelopeWriter.WriteObjectStart();
610 	requestEnvelopeWriter.WriteObjectEnd();
611 
612 	return _SendJsonRequest("captcha/generate-captcha",
613 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
614 		0, message);
615 }
616 
617 
618 status_t
619 WebAppInterface::CreateUser(const BString& nickName,
620 	const BString& passwordClear, const BString& email,
621 	const BString& captchaToken, const BString& captchaResponse,
622 	const BString& languageCode, const BString& userUsageConditionsCode,
623 	BMessage& message)
624 {
625 		// BHttpRequest later takes ownership of this.
626 	BMallocIO* requestEnvelopeData = new BMallocIO();
627 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
628 
629 	requestEnvelopeWriter.WriteObjectStart();
630 
631 	requestEnvelopeWriter.WriteObjectName("nickname");
632 	requestEnvelopeWriter.WriteString(nickName.String());
633 	requestEnvelopeWriter.WriteObjectName("passwordClear");
634 	requestEnvelopeWriter.WriteString(passwordClear.String());
635 	requestEnvelopeWriter.WriteObjectName("captchaToken");
636 	requestEnvelopeWriter.WriteString(captchaToken.String());
637 	requestEnvelopeWriter.WriteObjectName("captchaResponse");
638 	requestEnvelopeWriter.WriteString(captchaResponse.String());
639 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
640 	requestEnvelopeWriter.WriteString(languageCode.String());
641 	requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode");
642 	requestEnvelopeWriter.WriteString(userUsageConditionsCode.String());
643 
644 	if (!email.IsEmpty()) {
645 		requestEnvelopeWriter.WriteObjectName("email");
646 		requestEnvelopeWriter.WriteString(email.String());
647 	}
648 
649 	requestEnvelopeWriter.WriteObjectEnd();
650 
651 	return _SendJsonRequest("user/create-user", requestEnvelopeData,
652 		_LengthAndSeekToZero(requestEnvelopeData), 0, message);
653 }
654 
655 
656 status_t
657 WebAppInterface::AuthenticateUser(const BString& nickName,
658 	const BString& passwordClear, BMessage& message)
659 {
660 	BMallocIO* requestEnvelopeData = new BMallocIO();
661 		// BHttpRequest later takes ownership of this.
662 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
663 
664 	requestEnvelopeWriter.WriteObjectStart();
665 
666 	requestEnvelopeWriter.WriteObjectName("nickname");
667 	requestEnvelopeWriter.WriteString(nickName.String());
668 	requestEnvelopeWriter.WriteObjectName("passwordClear");
669 	requestEnvelopeWriter.WriteString(passwordClear.String());
670 
671 	requestEnvelopeWriter.WriteObjectEnd();
672 
673 	return _SendJsonRequest("user/authenticate-user",
674 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
675 		0, message);
676 }
677 
678 
679 status_t
680 WebAppInterface::IncrementViewCounter(const PackageInfoRef package,
681 	const DepotInfoRef depot, BMessage& message)
682 {
683 	BMallocIO* requestEnvelopeData = new BMallocIO();
684 		// BHttpRequest later takes ownership of this.
685 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
686 
687 	requestEnvelopeWriter.WriteObjectStart();
688 
689 	requestEnvelopeWriter.WriteObjectName("architectureCode");
690 	requestEnvelopeWriter.WriteString(package->Architecture());
691 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
692 	requestEnvelopeWriter.WriteString(depot->WebAppRepositoryCode());
693 	requestEnvelopeWriter.WriteObjectName("name");
694 	requestEnvelopeWriter.WriteString(package->Name());
695 
696 	const BPackageVersion version = package->Version();
697 	if (!version.Major().IsEmpty()) {
698 		requestEnvelopeWriter.WriteObjectName("major");
699 		requestEnvelopeWriter.WriteString(version.Major());
700 	}
701 	if (!version.Minor().IsEmpty()) {
702 		requestEnvelopeWriter.WriteObjectName("minor");
703 		requestEnvelopeWriter.WriteString(version.Minor());
704 	}
705 	if (!version.Micro().IsEmpty()) {
706 		requestEnvelopeWriter.WriteObjectName("micro");
707 		requestEnvelopeWriter.WriteString(version.Micro());
708 	}
709 	if (!version.PreRelease().IsEmpty()) {
710 		requestEnvelopeWriter.WriteObjectName("preRelease");
711 		requestEnvelopeWriter.WriteString(version.PreRelease());
712 	}
713 	if (version.Revision() != 0) {
714 		requestEnvelopeWriter.WriteObjectName("revision");
715 		requestEnvelopeWriter.WriteInteger(
716 			static_cast<int64>(version.Revision()));
717 	}
718 
719 	requestEnvelopeWriter.WriteObjectEnd();
720 
721 	return _SendJsonRequest("pkg/increment-view-counter",
722 		requestEnvelopeData, _LengthAndSeekToZero(requestEnvelopeData),
723 		0, message);
724 }
725 
726 
727 /*!	JSON-RPC invocations return a response.  The response may be either
728 	a result or it may be an error depending on the response structure.
729 	If it is an error then there may be additional detail that is the
730 	error code and message.  This method will extract the error code
731 	from the response.  This method will return 0 if the payload does
732 	not look like an error.
733 */
734 
735 int32
736 WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage)
737 {
738 	BMessage error;
739 	double code;
740 
741 	if (responseEnvelopeMessage.FindMessage("error", &error) == B_OK
742 		&& error.FindDouble("code", &code) == B_OK) {
743 		return (int32) code;
744 	}
745 
746 	return 0;
747 }
748 
749 
750 // #pragma mark - private
751 
752 
753 status_t
754 WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
755 	BPositionIO* requestData, size_t requestDataSize, uint32 flags,
756 	BMessage& reply) const
757 {
758 	return _SendJsonRequest(urlPathComponents, fCredentials, requestData,
759 		requestDataSize, flags, reply);
760 }
761 
762 
763 status_t
764 WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
765 	UserCredentials credentials, BPositionIO* requestData,
766 	size_t requestDataSize, uint32 flags, BMessage& reply) const
767 {
768 	if (requestDataSize == 0) {
769 		HDINFO("%s; empty request payload", PROTOCOL_NAME);
770 		return B_ERROR;
771 	}
772 
773 	if (!ServerHelper::IsNetworkAvailable()) {
774 		HDDEBUG("%s; dropping request to ...[%s] as network is not"
775 			" available", PROTOCOL_NAME, urlPathComponents);
776 		delete requestData;
777 		return HD_NETWORK_INACCESSIBLE;
778 	}
779 
780 	if (ServerSettings::IsClientTooOld()) {
781 		HDDEBUG("%s; dropping request to ...[%s] as client is too old",
782 			PROTOCOL_NAME, urlPathComponents);
783 		delete requestData;
784 		return HD_CLIENT_TOO_OLD;
785 	}
786 
787 	BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v2/")
788 		<< urlPathComponents);
789 	HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME,
790 		url.UrlString().String());
791 
792 	// If the request payload is logged then it must be copied to local memory
793 	// from the stream.  This then requires that the request data is then
794 	// delivered from memory.
795 
796 	if (Logger::IsTraceEnabled()) {
797 		HDLOGPREFIX(LOG_LEVEL_TRACE)
798 		printf("%s request; ", PROTOCOL_NAME);
799 		_LogPayload(requestData, requestDataSize);
800 		printf("\n");
801 	}
802 
803 	ProtocolListener listener;
804 	BUrlContext context;
805 
806 	BHttpHeaders headers;
807 	headers.AddHeader("Content-Type", "application/json");
808 	headers.AddHeader("Accept", "application/json");
809 	ServerSettings::AugmentHeaders(headers);
810 
811 	BHttpRequest* request = make_http_request(url, NULL, &listener, &context);
812 	ObjectDeleter<BHttpRequest> _(request);
813 	if (request == NULL)
814 		return B_ERROR;
815 	request->SetMethod(B_HTTP_POST);
816 	request->SetHeaders(headers);
817 
818 	// Authentication via Basic Authentication
819 	// The other way would be to obtain a token and then use the Token Bearer
820 	// header.
821 	if (((flags & NEEDS_AUTHORIZATION) != 0) && credentials.IsValid()) {
822 		BHttpAuthentication authentication(credentials.Nickname(),
823 			credentials.PasswordClear());
824 		authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC);
825 		context.AddAuthentication(url, authentication);
826 	}
827 
828 	request->AdoptInputData(requestData, requestDataSize);
829 
830 	BMallocIO replyData;
831 	request->SetOutput(&replyData);
832 
833 	thread_id thread = request->Run();
834 	wait_for_thread(thread, NULL);
835 
836 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
837 		request->Result());
838 
839 	int32 statusCode = result.StatusCode();
840 
841 	HDDEBUG("%s; did receive http-status [%" B_PRId32 "] from [%s]",
842 		PROTOCOL_NAME, statusCode, url.UrlString().String());
843 
844 	switch (statusCode) {
845 		case B_HTTP_STATUS_OK:
846 			break;
847 
848 		case B_HTTP_STATUS_PRECONDITION_FAILED:
849 			ServerHelper::NotifyClientTooOld(result.Headers());
850 			return HD_CLIENT_TOO_OLD;
851 
852 		default:
853 			HDERROR("%s; request to endpoint [.../%s] failed with http "
854 				"status [%" B_PRId32 "]\n", PROTOCOL_NAME, urlPathComponents,
855 				statusCode);
856 			return B_ERROR;
857 	}
858 
859 	replyData.Seek(0, SEEK_SET);
860 
861 	if (Logger::IsTraceEnabled()) {
862 		HDLOGPREFIX(LOG_LEVEL_TRACE)
863 		printf("%s; response; ", PROTOCOL_NAME);
864 		_LogPayload(&replyData, replyData.BufferLength());
865 		printf("\n");
866 	}
867 
868 	BJsonMessageWriter jsonMessageWriter(reply);
869 	BJson::Parse(&replyData, &jsonMessageWriter);
870 	status_t status = jsonMessageWriter.ErrorStatus();
871 
872 	if (Logger::IsTraceEnabled() && status == B_BAD_DATA) {
873 		BString resultString(static_cast<const char *>(replyData.Buffer()),
874 			replyData.BufferLength());
875 		HDERROR("Parser choked on JSON:\n%s", resultString.String());
876 	}
877 	return status;
878 }
879 
880 
881 status_t
882 WebAppInterface::_SendJsonRequest(const char* urlPathComponents,
883 	const BString& jsonString, uint32 flags, BMessage& reply) const
884 {
885 	// gets 'adopted' by the subsequent http request.
886 	BMemoryIO* data = new BMemoryIO(jsonString.String(),
887 		jsonString.Length() - 1);
888 
889 	return _SendJsonRequest(urlPathComponents, data, jsonString.Length() - 1,
890 		flags, reply);
891 }
892 
893 
894 status_t
895 WebAppInterface::_SendRawGetRequest(const BString urlPathComponents,
896 	BDataIO* stream)
897 {
898 	BUrl url = ServerSettings::CreateFullUrl(urlPathComponents);
899 
900 	ProtocolListener listener;
901 
902 	BHttpHeaders headers;
903 	ServerSettings::AugmentHeaders(headers);
904 
905 	BHttpRequest *request = make_http_request(url, stream, &listener);
906 	ObjectDeleter<BHttpRequest> _(request);
907 	if (request == NULL)
908 		return B_ERROR;
909 	request->SetMethod(B_HTTP_GET);
910 	request->SetHeaders(headers);
911 
912 	thread_id thread = request->Run();
913 	wait_for_thread(thread, NULL);
914 
915 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
916 		request->Result());
917 
918 	int32 statusCode = result.StatusCode();
919 
920 	if (statusCode == 200)
921 		return B_OK;
922 
923 	HDERROR("failed to get data from '%s': %" B_PRIi32 "",
924 		url.UrlString().String(), statusCode);
925 	return B_ERROR;
926 }
927 
928 
929 void
930 WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size)
931 {
932 	off_t requestDataOffset = requestData->Position();
933 	char buffer[LOG_PAYLOAD_LIMIT];
934 
935 	if (size > LOG_PAYLOAD_LIMIT)
936 		size = LOG_PAYLOAD_LIMIT;
937 
938 	if (B_OK != requestData->ReadExactly(buffer, size)) {
939 		printf("%s; error logging payload", PROTOCOL_NAME);
940 	} else {
941 		for (uint32 i = 0; i < size; i++) {
942     		bool esc = buffer[i] > 126 ||
943     			(buffer[i] < 0x20 && buffer[i] != 0x0a);
944 
945     		if (esc)
946     			printf("\\u%02x", buffer[i]);
947     		else
948     			putchar(buffer[i]);
949     	}
950 
951     	if (size == LOG_PAYLOAD_LIMIT)
952     		printf("...(continues)");
953 	}
954 
955 	requestData->Seek(requestDataOffset, SEEK_SET);
956 }
957 
958 
959 /*!	This will get the position of the data to get the length an then sets the
960 	offset to zero so that it can be re-read for reading the payload in to log
961 	or send.
962 */
963 
964 off_t
965 WebAppInterface::_LengthAndSeekToZero(BPositionIO* data)
966 {
967 	off_t dataSize = data->Position();
968     data->Seek(0, SEEK_SET);
969     return dataSize;
970 }
971