xref: /haiku/src/apps/haikudepot/server/WebAppInterface.cpp (revision 5bd0fbd13a1e832f91643aaa921fbc0879abd518)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2016-2018, 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 	fLanguage("en")
276 {
277 }
278 
279 
280 WebAppInterface::WebAppInterface(const WebAppInterface& other)
281 	:
282 	fUsername(other.fUsername),
283 	fPassword(other.fPassword),
284 	fLanguage(other.fLanguage)
285 {
286 }
287 
288 
289 WebAppInterface::~WebAppInterface()
290 {
291 }
292 
293 
294 WebAppInterface&
295 WebAppInterface::operator=(const WebAppInterface& other)
296 {
297 	if (this == &other)
298 		return *this;
299 
300 	fUsername = other.fUsername;
301 	fPassword = other.fPassword;
302 	fLanguage = other.fLanguage;
303 
304 	return *this;
305 }
306 
307 
308 void
309 WebAppInterface::SetAuthorization(const BString& username,
310 	const BString& password)
311 {
312 	fUsername = username;
313 	fPassword = password;
314 }
315 
316 
317 void
318 WebAppInterface::SetPreferredLanguage(const BString& language)
319 {
320 	fLanguage = language;
321 }
322 
323 
324 status_t
325 WebAppInterface::GetChangelog(const BString& packageName, BMessage& message)
326 {
327 	BString jsonString = JsonBuilder()
328 		.AddValue("jsonrpc", "2.0")
329 		.AddValue("id", ++fRequestIndex)
330 		.AddValue("method", "getPkgChangelog")
331 		.AddArray("params")
332 			.AddObject()
333 				.AddValue("pkgName", packageName)
334 			.EndObject()
335 		.EndArray()
336 	.End();
337 
338 	return _SendJsonRequest("pkg", jsonString, 0, message);
339 }
340 
341 
342 status_t
343 WebAppInterface::RetrieveUserRatings(const BString& packageName,
344 	const BString& architecture, int resultOffset, int maxResults,
345 	BMessage& message)
346 {
347 	BString jsonString = JsonBuilder()
348 		.AddValue("jsonrpc", "2.0")
349 		.AddValue("id", ++fRequestIndex)
350 		.AddValue("method", "searchUserRatings")
351 		.AddArray("params")
352 			.AddObject()
353 				.AddValue("pkgName", packageName)
354 				.AddValue("pkgVersionArchitectureCode", architecture)
355 				.AddValue("offset", resultOffset)
356 				.AddValue("limit", maxResults)
357 			.EndObject()
358 		.EndArray()
359 	.End();
360 
361 	return _SendJsonRequest("userrating", jsonString, 0, message);
362 }
363 
364 
365 status_t
366 WebAppInterface::RetrieveUserRating(const BString& packageName,
367 	const BPackageVersion& version, const BString& architecture,
368 	const BString &repositoryCode, const BString& username,
369 	BMessage& message)
370 {
371 		// BHttpRequest later takes ownership of this.
372 	BMallocIO* requestEnvelopeData = new BMallocIO();
373 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
374 
375 	requestEnvelopeWriter.WriteObjectStart();
376 	_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
377 		"getUserRatingByUserAndPkgVersion");
378 	requestEnvelopeWriter.WriteObjectName("params");
379 	requestEnvelopeWriter.WriteArrayStart();
380 
381 	requestEnvelopeWriter.WriteObjectStart();
382 
383 	requestEnvelopeWriter.WriteObjectName("userNickname");
384 	requestEnvelopeWriter.WriteString(username.String());
385 	requestEnvelopeWriter.WriteObjectName("pkgName");
386 	requestEnvelopeWriter.WriteString(packageName.String());
387 	requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
388 	requestEnvelopeWriter.WriteString(architecture.String());
389 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
390 	requestEnvelopeWriter.WriteString(repositoryCode.String());
391 
392 	if (version.Major().Length() > 0) {
393 		requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
394 		requestEnvelopeWriter.WriteString(version.Major().String());
395 	}
396 
397 	if (version.Minor().Length() > 0) {
398 		requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
399 		requestEnvelopeWriter.WriteString(version.Minor().String());
400 	}
401 
402 	if (version.Micro().Length() > 0) {
403 		requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
404 		requestEnvelopeWriter.WriteString(version.Micro().String());
405 	}
406 
407 	if (version.PreRelease().Length() > 0) {
408 		requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
409 		requestEnvelopeWriter.WriteString(version.PreRelease().String());
410 	}
411 
412 	if (version.Revision() != 0) {
413 		requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
414 		requestEnvelopeWriter.WriteInteger(version.Revision());
415 	}
416 
417 	requestEnvelopeWriter.WriteObjectEnd();
418 	requestEnvelopeWriter.WriteArrayEnd();
419 	requestEnvelopeWriter.WriteObjectEnd();
420 
421 	return _SendJsonRequest("userrating", requestEnvelopeData,
422 		_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
423 		message);
424 }
425 
426 
427 status_t
428 WebAppInterface::CreateUserRating(const BString& packageName,
429 	const BPackageVersion& version,
430 	const BString& architecture, const BString& repositoryCode,
431 	const BString& languageCode, const BString& comment,
432 	const BString& stability, int rating, BMessage& message)
433 {
434 		// BHttpRequest later takes ownership of this.
435 	BMallocIO* requestEnvelopeData = new BMallocIO();
436 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
437 
438 	requestEnvelopeWriter.WriteObjectStart();
439 	_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
440 		"createUserRating");
441 	requestEnvelopeWriter.WriteObjectName("params");
442 	requestEnvelopeWriter.WriteArrayStart();
443 
444 	requestEnvelopeWriter.WriteObjectStart();
445 	requestEnvelopeWriter.WriteObjectName("pkgName");
446 	requestEnvelopeWriter.WriteString(packageName.String());
447 	requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode");
448 	requestEnvelopeWriter.WriteString(architecture.String());
449 	requestEnvelopeWriter.WriteObjectName("repositoryCode");
450 	requestEnvelopeWriter.WriteString(repositoryCode.String());
451 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
452 	requestEnvelopeWriter.WriteString(languageCode.String());
453 	requestEnvelopeWriter.WriteObjectName("pkgVersionType");
454 	requestEnvelopeWriter.WriteString("SPECIFIC");
455 	requestEnvelopeWriter.WriteObjectName("userNickname");
456 	requestEnvelopeWriter.WriteString(fUsername.String());
457 
458 	if (!version.Major().IsEmpty()) {
459 		requestEnvelopeWriter.WriteObjectName("pkgVersionMajor");
460 		requestEnvelopeWriter.WriteString(version.Major());
461 	}
462 
463 	if (!version.Minor().IsEmpty()) {
464 		requestEnvelopeWriter.WriteObjectName("pkgVersionMinor");
465 		requestEnvelopeWriter.WriteString(version.Minor());
466 	}
467 
468 	if (!version.Micro().IsEmpty()) {
469 		requestEnvelopeWriter.WriteObjectName("pkgVersionMicro");
470 		requestEnvelopeWriter.WriteString(version.Micro());
471 	}
472 
473 	if (!version.PreRelease().IsEmpty()) {
474 		requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease");
475 		requestEnvelopeWriter.WriteString(version.PreRelease());
476 	}
477 
478 	if (version.Revision() != 0) {
479 		requestEnvelopeWriter.WriteObjectName("pkgVersionRevision");
480 		requestEnvelopeWriter.WriteInteger(version.Revision());
481 	}
482 
483 	if (rating > 0.0f) {
484 		requestEnvelopeWriter.WriteObjectName("rating");
485     	requestEnvelopeWriter.WriteInteger(rating);
486 	}
487 
488 	if (stability.Length() > 0) {
489 		requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
490 		requestEnvelopeWriter.WriteString(stability);
491 	}
492 
493 	if (comment.Length() > 0) {
494 		requestEnvelopeWriter.WriteObjectName("comment");
495 		requestEnvelopeWriter.WriteString(comment.String());
496 	}
497 
498 	requestEnvelopeWriter.WriteObjectEnd();
499 	requestEnvelopeWriter.WriteArrayEnd();
500 	requestEnvelopeWriter.WriteObjectEnd();
501 
502 	return _SendJsonRequest("userrating", requestEnvelopeData,
503 		_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
504 		message);
505 }
506 
507 
508 status_t
509 WebAppInterface::UpdateUserRating(const BString& ratingID,
510 	const BString& languageCode, const BString& comment,
511 	const BString& stability, int rating, bool active, BMessage& message)
512 {
513 		// BHttpRequest later takes ownership of this.
514 	BMallocIO* requestEnvelopeData = new BMallocIO();
515 	BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData);
516 
517 	requestEnvelopeWriter.WriteObjectStart();
518 	_WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter,
519 		"updateUserRating");
520 
521 	requestEnvelopeWriter.WriteObjectName("params");
522 	requestEnvelopeWriter.WriteArrayStart();
523 
524 	requestEnvelopeWriter.WriteObjectStart();
525 
526 	requestEnvelopeWriter.WriteObjectName("code");
527 	requestEnvelopeWriter.WriteString(ratingID.String());
528 	requestEnvelopeWriter.WriteObjectName("naturalLanguageCode");
529 	requestEnvelopeWriter.WriteString(languageCode.String());
530 	requestEnvelopeWriter.WriteObjectName("active");
531 	requestEnvelopeWriter.WriteBoolean(active);
532 
533 	requestEnvelopeWriter.WriteObjectName("filter");
534 	requestEnvelopeWriter.WriteArrayStart();
535 	requestEnvelopeWriter.WriteString("ACTIVE");
536 	requestEnvelopeWriter.WriteString("NATURALLANGUAGE");
537 	requestEnvelopeWriter.WriteString("USERRATINGSTABILITY");
538 	requestEnvelopeWriter.WriteString("COMMENT");
539 	requestEnvelopeWriter.WriteString("RATING");
540 	requestEnvelopeWriter.WriteArrayEnd();
541 
542 	if (rating >= 0) {
543 		requestEnvelopeWriter.WriteObjectName("rating");
544 		requestEnvelopeWriter.WriteInteger(rating);
545 	}
546 
547 	if (stability.Length() > 0) {
548 		requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode");
549 		requestEnvelopeWriter.WriteString(stability);
550 	}
551 
552 	if (comment.Length() > 0) {
553 		requestEnvelopeWriter.WriteObjectName("comment");
554 		requestEnvelopeWriter.WriteString(comment);
555 	}
556 
557 	requestEnvelopeWriter.WriteObjectEnd();
558 	requestEnvelopeWriter.WriteArrayEnd();
559 	requestEnvelopeWriter.WriteObjectEnd();
560 
561 	return _SendJsonRequest("userrating", requestEnvelopeData,
562 		_LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION,
563 		message);
564 }
565 
566 
567 status_t
568 WebAppInterface::RetrieveScreenshot(const BString& code,
569 	int32 width, int32 height, BDataIO* stream)
570 {
571 	BUrl url = ServerSettings::CreateFullUrl(
572 		BString("/__pkgscreenshot/") << code << ".png" << "?tw="
573 			<< width << "&th=" << height);
574 
575 	bool isSecure = url.Protocol() == "https";
576 
577 	ProtocolListener listener(Logger::IsTraceEnabled());
578 	listener.SetDownloadIO(stream);
579 
580 	BHttpHeaders headers;
581 	ServerSettings::AugmentHeaders(headers);
582 
583 	BHttpRequest request(url, isSecure, "HTTP", &listener);
584 	request.SetMethod(B_HTTP_GET);
585 	request.SetHeaders(headers);
586 
587 	thread_id thread = request.Run();
588 	wait_for_thread(thread, NULL);
589 
590 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
591 		request.Result());
592 
593 	int32 statusCode = result.StatusCode();
594 
595 	if (statusCode == 200)
596 		return B_OK;
597 
598 	fprintf(stderr, "failed to get screenshot from '%s': %" B_PRIi32 "\n",
599 		url.UrlString().String(), statusCode);
600 	return B_ERROR;
601 }
602 
603 
604 status_t
605 WebAppInterface::RequestCaptcha(BMessage& message)
606 {
607 	BString jsonString = JsonBuilder()
608 		.AddValue("jsonrpc", "2.0")
609 		.AddValue("id", ++fRequestIndex)
610 		.AddValue("method", "generateCaptcha")
611 		.AddArray("params")
612 			.AddObject()
613 			.EndObject()
614 		.EndArray()
615 	.End();
616 
617 	return _SendJsonRequest("captcha", jsonString, 0, message);
618 }
619 
620 
621 status_t
622 WebAppInterface::CreateUser(const BString& nickName,
623 	const BString& passwordClear, const BString& email,
624 	const BString& captchaToken, const BString& captchaResponse,
625 	const BString& languageCode, BMessage& message)
626 {
627 	JsonBuilder builder;
628 	builder
629 		.AddValue("jsonrpc", "2.0")
630 		.AddValue("id", ++fRequestIndex)
631 		.AddValue("method", "createUser")
632 		.AddArray("params")
633 			.AddObject()
634 				.AddValue("nickname", nickName)
635 				.AddValue("passwordClear", passwordClear);
636 
637 				if (!email.IsEmpty())
638 					builder.AddValue("email", email);
639 
640 				builder.AddValue("captchaToken", captchaToken)
641 				.AddValue("captchaResponse", captchaResponse)
642 				.AddValue("naturalLanguageCode", languageCode)
643 			.EndObject()
644 		.EndArray()
645 	;
646 
647 	BString jsonString = builder.End();
648 
649 	return _SendJsonRequest("user", jsonString, 0, message);
650 }
651 
652 
653 status_t
654 WebAppInterface::AuthenticateUser(const BString& nickName,
655 	const BString& passwordClear, BMessage& message)
656 {
657 	BString jsonString = JsonBuilder()
658 		.AddValue("jsonrpc", "2.0")
659 		.AddValue("id", ++fRequestIndex)
660 		.AddValue("method", "authenticateUser")
661 		.AddArray("params")
662 			.AddObject()
663 				.AddValue("nickname", nickName)
664 				.AddValue("passwordClear", passwordClear)
665 			.EndObject()
666 		.EndArray()
667 	.End();
668 
669 	return _SendJsonRequest("user", jsonString, 0, message);
670 }
671 
672 
673 /*! JSON-RPC invocations return a response.  The response may be either
674     a result or it may be an error depending on the response structure.
675     If it is an error then there may be additional detail that is the
676     error code and message.  This method will extract the error code
677     from the response.  This method will return 0 if the payload does
678     not look like an error.
679 */
680 
681 int32
682 WebAppInterface::ErrorCodeFromResponse(BMessage& response)
683 {
684 	BMessage error;
685 	double code;
686 
687 	if (response.FindMessage("error", &error) == B_OK
688 		&& error.FindDouble("code", &code) == B_OK) {
689 		return (int32) code;
690 	}
691 
692 	return 0;
693 }
694 
695 
696 // #pragma mark - private
697 
698 
699 void
700 WebAppInterface::_WriteStandardJsonRpcEnvelopeValues(BJsonWriter& writer,
701 	const char* methodName)
702 {
703 	writer.WriteObjectName("jsonrpc");
704 	writer.WriteString("2.0");
705 	writer.WriteObjectName("id");
706 	writer.WriteInteger(++fRequestIndex);
707 	writer.WriteObjectName("method");
708 	writer.WriteString(methodName);
709 }
710 
711 
712 status_t
713 WebAppInterface::_SendJsonRequest(const char* domain, BPositionIO* requestData,
714 	size_t requestDataSize, uint32 flags, BMessage& reply) const
715 {
716 	if (requestDataSize == 0) {
717 		if (Logger::IsInfoEnabled())
718 			printf("jrpc; empty request payload\n");
719 		return B_ERROR;
720 	}
721 
722 	if (!ServerHelper::IsNetworkAvailable()) {
723 		if (Logger::IsDebugEnabled()) {
724 			printf("jrpc; dropping request to ...[%s] as network is not "
725 				"available\n", domain);
726 		}
727 		delete requestData;
728 		return HD_NETWORK_INACCESSIBLE;
729 	}
730 
731 	if (ServerSettings::IsClientTooOld()) {
732 		if (Logger::IsDebugEnabled()) {
733 			printf("jrpc; dropping request to ...[%s] as client is too "
734 				"old\n", domain);
735 		}
736 		delete requestData;
737 		return HD_CLIENT_TOO_OLD;
738 	}
739 
740 	BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v1/") << domain);
741 	bool isSecure = url.Protocol() == "https";
742 
743 	if (Logger::IsDebugEnabled()) {
744 		printf("jrpc; will make request to [%s]\n",
745 			url.UrlString().String());
746 	}
747 
748 	// If the request payload is logged then it must be copied to local memory
749 	// from the stream.  This then requires that the request data is then
750 	// delivered from memory.
751 
752 	if (Logger::IsTraceEnabled()) {
753 		printf("jrpc request; ");
754 		_LogPayload(requestData, requestDataSize);
755 		printf("\n");
756 	}
757 
758 	ProtocolListener listener(Logger::IsTraceEnabled());
759 	BUrlContext context;
760 
761 	BHttpHeaders headers;
762 	headers.AddHeader("Content-Type", "application/json");
763 	ServerSettings::AugmentHeaders(headers);
764 
765 	BHttpRequest request(url, isSecure, "HTTP", &listener, &context);
766 	request.SetMethod(B_HTTP_POST);
767 	request.SetHeaders(headers);
768 
769 	// Authentication via Basic Authentication
770 	// The other way would be to obtain a token and then use the Token Bearer
771 	// header.
772 	if ((flags & NEEDS_AUTHORIZATION) != 0
773 		&& !fUsername.IsEmpty() && !fPassword.IsEmpty()) {
774 		BHttpAuthentication authentication(fUsername, fPassword);
775 		authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC);
776 		context.AddAuthentication(url, authentication);
777 	}
778 
779 
780 	request.AdoptInputData(requestData, requestDataSize);
781 
782 	BMallocIO replyData;
783 	listener.SetDownloadIO(&replyData);
784 
785 	thread_id thread = request.Run();
786 	wait_for_thread(thread, NULL);
787 
788 	const BHttpResult& result = dynamic_cast<const BHttpResult&>(
789 		request.Result());
790 
791 	int32 statusCode = result.StatusCode();
792 
793 	if (Logger::IsDebugEnabled()) {
794 		printf("jrpc; did receive http-status [%" B_PRId32 "] "
795 			"from [%s]\n", statusCode, url.UrlString().String());
796 	}
797 
798 	switch (statusCode) {
799 		case B_HTTP_STATUS_OK:
800 			break;
801 
802 		case B_HTTP_STATUS_PRECONDITION_FAILED:
803 			ServerHelper::NotifyClientTooOld(result.Headers());
804 			return HD_CLIENT_TOO_OLD;
805 
806 		default:
807 			printf("jrpc request to endpoint [.../%s] failed with http "
808 				"status [%" B_PRId32 "]\n", domain, statusCode);
809 			return B_ERROR;
810 	}
811 
812 	replyData.Seek(0, SEEK_SET);
813 
814 	if (Logger::IsTraceEnabled()) {
815 		printf("jrpc response; ");
816 		_LogPayload(&replyData, replyData.BufferLength());
817 		printf("\n");
818 	}
819 
820 	BJsonMessageWriter jsonMessageWriter(reply);
821 	BJson::Parse(&replyData, &jsonMessageWriter);
822 	status_t status = jsonMessageWriter.ErrorStatus();
823 
824 	if (Logger::IsTraceEnabled() && status == B_BAD_DATA) {
825 		BString resultString(static_cast<const char *>(replyData.Buffer()),
826 			replyData.BufferLength());
827 		printf("Parser choked on JSON:\n%s\n", resultString.String());
828 	}
829 	return status;
830 }
831 
832 
833 status_t
834 WebAppInterface::_SendJsonRequest(const char* domain, const BString& jsonString,
835 	uint32 flags, BMessage& reply) const
836 {
837 	// gets 'adopted' by the subsequent http request.
838 	BMemoryIO* data = new BMemoryIO(jsonString.String(),
839 		jsonString.Length() - 1);
840 
841 	return _SendJsonRequest(domain, data, jsonString.Length() - 1, flags,
842 		reply);
843 }
844 
845 
846 void
847 WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size)
848 {
849 	off_t requestDataOffset = requestData->Position();
850 	char buffer[LOG_PAYLOAD_LIMIT];
851 
852 	if (size > LOG_PAYLOAD_LIMIT)
853 		size = LOG_PAYLOAD_LIMIT;
854 
855 	if (B_OK != requestData->ReadExactly(buffer, size)) {
856 		printf("jrpc; error logging payload\n");
857 	} else {
858 		for (uint32 i = 0; i < size; i++) {
859     		bool esc = buffer[i] > 126 ||
860     			(buffer[i] < 0x20 && buffer[i] != 0x0a);
861 
862     		if (esc)
863     			printf("\\u%02x", buffer[i]);
864     		else
865     			putchar(buffer[i]);
866     	}
867 
868     	if (size == LOG_PAYLOAD_LIMIT)
869     		printf("...(continues)");
870 	}
871 
872 	requestData->Seek(requestDataOffset, SEEK_SET);
873 }
874 
875 
876 /*! This will get the position of the data to get the length an then sets the
877     offset to zero so that it can be re-read for reading the payload in to log
878     or send.
879 */
880 
881 off_t
882 WebAppInterface::_LengthAndSeekToZero(BPositionIO* data)
883 {
884 	off_t dataSize = data->Position();
885     data->Seek(0, SEEK_SET);
886     return dataSize;
887 }
888