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