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