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