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