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