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