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