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