2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2016-2017, Andrew Lindesay <apl@lindesay.co.nz>.
4 * All rights reserved. Distributed under the terms of the MIT License.
7 #include "WebAppInterface.h"
11 #include <AppFileInfo.h>
12 #include <Application.h>
15 #include <HttpHeaders.h>
16 #include <HttpRequest.h>
21 #include <UrlContext.h>
22 #include <UrlProtocolListener.h>
23 #include <UrlProtocolRoster.h>
25 #include "AutoLocker.h"
28 #include "PackageInfo.h"
29 #include "ServerSettings.h"
32 #define BASEURL_DEFAULT "https://depot.haiku-os.org"
33 #define USERAGENT_FALLBACK_VERSION "0.0.0"
45 JsonBuilder
& AddObject()
52 JsonBuilder
& AddObject(const char* name
)
60 JsonBuilder
& EndObject()
67 JsonBuilder
& AddArray(const char* name
)
75 JsonBuilder
& EndArray()
82 JsonBuilder
& AddStrings(const StringList
& strings
)
84 for (int i
= 0; i
< strings
.CountItems(); i
++)
85 AddItem(strings
.ItemAtFast(i
));
89 JsonBuilder
& AddItem(const char* item
)
91 return AddItem(item
, false);
94 JsonBuilder
& AddItem(const char* item
, bool nullIfEmpty
)
96 if (item
== NULL
|| (nullIfEmpty
&& strlen(item
) == 0)) {
106 fString
<< _EscapeString(item
);
113 JsonBuilder
& AddValue(const char* name
, const char* value
)
115 return AddValue(name
, value
, false);
118 JsonBuilder
& AddValue(const char* name
, const char* value
,
122 if (value
== NULL
|| (nullIfEmpty
&& strlen(value
) == 0)) {
126 fString
<< _EscapeString(value
);
133 JsonBuilder
& AddValue(const char* name
, int value
)
141 JsonBuilder
& AddValue(const char* name
, bool value
)
159 void _StartName(const char* name
)
165 fString
<< _EscapeString(name
);
169 BString
_EscapeString(const char* original
) const
171 BString
string(original
);
172 string
.ReplaceAll("\\", "\\\\");
173 string
.ReplaceAll("\"", "\\\"");
174 string
.ReplaceAll("/", "\\/");
175 string
.ReplaceAll("\b", "\\b");
176 string
.ReplaceAll("\f", "\\f");
177 string
.ReplaceAll("\n", "\\n");
178 string
.ReplaceAll("\r", "\\r");
179 string
.ReplaceAll("\t", "\\t");
189 class ProtocolListener
: public BUrlProtocolListener
{
191 ProtocolListener(bool traceLogging
)
194 fTraceLogging(traceLogging
)
198 virtual ~ProtocolListener()
202 virtual void ConnectionOpened(BUrlRequest
* caller
)
206 virtual void HostnameResolved(BUrlRequest
* caller
, const char* ip
)
210 virtual void ResponseStarted(BUrlRequest
* caller
)
214 virtual void HeadersReceived(BUrlRequest
* caller
, const BUrlResult
& result
)
218 virtual void DataReceived(BUrlRequest
* caller
, const char* data
,
219 off_t position
, ssize_t size
)
221 if (fDownloadIO
!= NULL
)
222 fDownloadIO
->Write(data
, size
);
225 virtual void DownloadProgress(BUrlRequest
* caller
, ssize_t bytesReceived
,
230 virtual void UploadProgress(BUrlRequest
* caller
, ssize_t bytesSent
,
235 virtual void RequestCompleted(BUrlRequest
* caller
, bool success
)
239 virtual void DebugMessage(BUrlRequest
* caller
,
240 BUrlProtocolDebugMessage type
, const char* text
)
243 printf("jrpc: %s\n", text
);
246 void SetDownloadIO(BDataIO
* downloadIO
)
248 fDownloadIO
= downloadIO
;
252 BDataIO
* fDownloadIO
;
258 WebAppInterface::fRequestIndex
= 0;
262 NEEDS_AUTHORIZATION
= 1 << 0,
266 WebAppInterface::WebAppInterface()
273 WebAppInterface::WebAppInterface(const WebAppInterface
& other
)
275 fUsername(other
.fUsername
),
276 fPassword(other
.fPassword
),
277 fLanguage(other
.fLanguage
)
282 WebAppInterface::~WebAppInterface()
288 WebAppInterface::operator=(const WebAppInterface
& other
)
293 fUsername
= other
.fUsername
;
294 fPassword
= other
.fPassword
;
295 fLanguage
= other
.fLanguage
;
302 WebAppInterface::SetAuthorization(const BString
& username
,
303 const BString
& password
)
305 fUsername
= username
;
306 fPassword
= password
;
311 WebAppInterface::SetPreferredLanguage(const BString
& language
)
313 fLanguage
= language
;
318 WebAppInterface::RetrieveRepositoriesForSourceBaseURLs(
319 const StringList
& repositorySourceBaseURLs
,
322 BString jsonString
= JsonBuilder()
323 .AddValue("jsonrpc", "2.0")
324 .AddValue("id", ++fRequestIndex
)
325 .AddValue("method", "searchRepositories")
328 .AddArray("repositorySourceSearchUrls")
329 .AddStrings(repositorySourceBaseURLs
)
331 .AddValue("offset", 0)
332 .AddValue("limit", 1000) // effectively a safety limit
337 return _SendJsonRequest("repository", jsonString
, 0, message
);
342 WebAppInterface::RetrievePackageInfo(const BString
& packageName
,
343 const BString
& architecture
, const BString
& repositoryCode
,
346 BString jsonString
= JsonBuilder()
347 .AddValue("jsonrpc", "2.0")
348 .AddValue("id", ++fRequestIndex
)
349 .AddValue("method", "getPkg")
352 .AddValue("name", packageName
)
353 .AddValue("architectureCode", architecture
)
354 .AddValue("naturalLanguageCode", fLanguage
)
355 .AddValue("repositoryCode", repositoryCode
)
356 .AddValue("versionType", "NONE")
361 return _SendJsonRequest("pkg", jsonString
, 0, message
);
366 WebAppInterface::RetrieveBulkPackageInfo(const StringList
& packageNames
,
367 const StringList
& packageArchitectures
,
368 const StringList
& repositoryCodes
, BMessage
& message
)
370 BString jsonString
= JsonBuilder()
371 .AddValue("jsonrpc", "2.0")
372 .AddValue("id", ++fRequestIndex
)
373 .AddValue("method", "getBulkPkg")
376 .AddArray("pkgNames")
377 .AddStrings(packageNames
)
379 .AddArray("architectureCodes")
380 .AddStrings(packageArchitectures
)
382 .AddArray("repositoryCodes")
383 .AddStrings(repositoryCodes
)
385 .AddValue("naturalLanguageCode", fLanguage
)
386 .AddValue("versionType", "LATEST")
388 .AddItem("PKGCATEGORIES")
389 .AddItem("PKGSCREENSHOTS")
390 .AddItem("PKGVERSIONLOCALIZATIONDESCRIPTIONS")
391 .AddItem("PKGCHANGELOG")
397 return _SendJsonRequest("pkg", jsonString
, 0, message
);
402 WebAppInterface::RetrieveUserRatings(const BString
& packageName
,
403 const BString
& architecture
, int resultOffset
, int maxResults
,
406 BString jsonString
= JsonBuilder()
407 .AddValue("jsonrpc", "2.0")
408 .AddValue("id", ++fRequestIndex
)
409 .AddValue("method", "searchUserRatings")
412 .AddValue("pkgName", packageName
)
413 .AddValue("pkgVersionArchitectureCode", architecture
)
414 .AddValue("offset", resultOffset
)
415 .AddValue("limit", maxResults
)
420 return _SendJsonRequest("userrating", jsonString
, 0, message
);
425 WebAppInterface::RetrieveUserRating(const BString
& packageName
,
426 const BPackageVersion
& version
, const BString
& architecture
,
427 const BString
&repositoryCode
, const BString
& username
,
430 BString jsonString
= JsonBuilder()
431 .AddValue("jsonrpc", "2.0")
432 .AddValue("id", ++fRequestIndex
)
433 .AddValue("method", "getUserRatingByUserAndPkgVersion")
436 .AddValue("userNickname", username
)
437 .AddValue("pkgName", packageName
)
438 .AddValue("pkgVersionArchitectureCode", architecture
)
439 .AddValue("pkgVersionMajor", version
.Major(), true)
440 .AddValue("pkgVersionMinor", version
.Minor(), true)
441 .AddValue("pkgVersionMicro", version
.Micro(), true)
442 .AddValue("pkgVersionPreRelease", version
.PreRelease(), true)
443 .AddValue("pkgVersionRevision", (int)version
.Revision())
444 .AddValue("repositoryCode", repositoryCode
)
449 return _SendJsonRequest("userrating", jsonString
, 0, message
);
454 WebAppInterface::CreateUserRating(const BString
& packageName
,
455 const BString
& architecture
, const BString
& repositoryCode
,
456 const BString
& languageCode
, const BString
& comment
,
457 const BString
& stability
, int rating
, BMessage
& message
)
459 BString jsonString
= JsonBuilder()
460 .AddValue("jsonrpc", "2.0")
461 .AddValue("id", ++fRequestIndex
)
462 .AddValue("method", "createUserRating")
465 .AddValue("pkgName", packageName
)
466 .AddValue("pkgVersionArchitectureCode", architecture
)
467 .AddValue("pkgVersionType", "LATEST")
468 .AddValue("userNickname", fUsername
)
469 .AddValue("rating", rating
)
470 .AddValue("userRatingStabilityCode", stability
, true)
471 .AddValue("comment", comment
)
472 .AddValue("repositoryCode", repositoryCode
)
473 .AddValue("naturalLanguageCode", languageCode
)
478 return _SendJsonRequest("userrating", jsonString
, NEEDS_AUTHORIZATION
,
484 WebAppInterface::UpdateUserRating(const BString
& ratingID
,
485 const BString
& languageCode
, const BString
& comment
,
486 const BString
& stability
, int rating
, bool active
, BMessage
& message
)
488 BString jsonString
= JsonBuilder()
489 .AddValue("jsonrpc", "2.0")
490 .AddValue("id", ++fRequestIndex
)
491 .AddValue("method", "updateUserRating")
494 .AddValue("code", ratingID
)
495 .AddValue("rating", rating
)
496 .AddValue("userRatingStabilityCode", stability
, true)
497 .AddValue("comment", comment
)
498 .AddValue("naturalLanguageCode", languageCode
)
499 .AddValue("active", active
)
502 .AddItem("NATURALLANGUAGE")
503 .AddItem("USERRATINGSTABILITY")
511 return _SendJsonRequest("userrating", jsonString
, NEEDS_AUTHORIZATION
,
517 WebAppInterface::RetrieveScreenshot(const BString
& code
,
518 int32 width
, int32 height
, BDataIO
* stream
)
520 BUrl url
= ServerSettings::CreateFullUrl(
521 BString("/__pkgscreenshot/") << code
<< ".png" << "?tw="
522 << width
<< "&th=" << height
);
524 bool isSecure
= url
.Protocol() == "https";
526 ProtocolListener
listener(Logger::IsTraceEnabled());
527 listener
.SetDownloadIO(stream
);
529 BHttpHeaders headers
;
530 ServerSettings::AugmentHeaders(headers
);
532 BHttpRequest
request(url
, isSecure
, "HTTP", &listener
);
533 request
.SetMethod(B_HTTP_GET
);
534 request
.SetHeaders(headers
);
536 thread_id thread
= request
.Run();
537 wait_for_thread(thread
, NULL
);
539 const BHttpResult
& result
= dynamic_cast<const BHttpResult
&>(
542 int32 statusCode
= result
.StatusCode();
544 if (statusCode
== 200)
547 fprintf(stderr
, "failed to get screenshot from '%s': %" B_PRIi32
"\n",
548 url
.UrlString().String(), statusCode
);
554 WebAppInterface::RequestCaptcha(BMessage
& message
)
556 BString jsonString
= JsonBuilder()
557 .AddValue("jsonrpc", "2.0")
558 .AddValue("id", ++fRequestIndex
)
559 .AddValue("method", "generateCaptcha")
566 return _SendJsonRequest("captcha", jsonString
, 0, message
);
571 WebAppInterface::CreateUser(const BString
& nickName
,
572 const BString
& passwordClear
, const BString
& email
,
573 const BString
& captchaToken
, const BString
& captchaResponse
,
574 const BString
& languageCode
, BMessage
& message
)
578 .AddValue("jsonrpc", "2.0")
579 .AddValue("id", ++fRequestIndex
)
580 .AddValue("method", "createUser")
583 .AddValue("nickname", nickName
)
584 .AddValue("passwordClear", passwordClear
);
586 if (!email
.IsEmpty())
587 builder
.AddValue("email", email
);
589 builder
.AddValue("captchaToken", captchaToken
)
590 .AddValue("captchaResponse", captchaResponse
)
591 .AddValue("naturalLanguageCode", languageCode
)
596 BString jsonString
= builder
.End();
598 return _SendJsonRequest("user", jsonString
, 0, message
);
603 WebAppInterface::AuthenticateUser(const BString
& nickName
,
604 const BString
& passwordClear
, BMessage
& message
)
606 BString jsonString
= JsonBuilder()
607 .AddValue("jsonrpc", "2.0")
608 .AddValue("id", ++fRequestIndex
)
609 .AddValue("method", "authenticateUser")
612 .AddValue("nickname", nickName
)
613 .AddValue("passwordClear", passwordClear
)
618 return _SendJsonRequest("user", jsonString
, 0, message
);
622 // #pragma mark - private
626 WebAppInterface::_SendJsonRequest(const char* domain
, BString jsonString
,
627 uint32 flags
, BMessage
& reply
) const
629 if (Logger::IsTraceEnabled())
630 printf("_SendJsonRequest(%s)\n", jsonString
.String());
632 BUrl url
= ServerSettings::CreateFullUrl(BString("/__api/v1/") << domain
);
633 bool isSecure
= url
.Protocol() == "https";
635 ProtocolListener
listener(Logger::IsTraceEnabled());
638 BHttpHeaders headers
;
639 headers
.AddHeader("Content-Type", "application/json");
640 ServerSettings::AugmentHeaders(headers
);
642 BHttpRequest
request(url
, isSecure
, "HTTP", &listener
, &context
);
643 request
.SetMethod(B_HTTP_POST
);
644 request
.SetHeaders(headers
);
646 // Authentication via Basic Authentication
647 // The other way would be to obtain a token and then use the Token Bearer
649 if ((flags
& NEEDS_AUTHORIZATION
) != 0
650 && !fUsername
.IsEmpty() && !fPassword
.IsEmpty()) {
651 BHttpAuthentication
authentication(fUsername
, fPassword
);
652 authentication
.SetMethod(B_HTTP_AUTHENTICATION_BASIC
);
653 context
.AddAuthentication(url
, authentication
);
656 BMemoryIO
* data
= new BMemoryIO(
657 jsonString
.String(), jsonString
.Length() - 1);
659 request
.AdoptInputData(data
, jsonString
.Length() - 1);
662 listener
.SetDownloadIO(&replyData
);
664 thread_id thread
= request
.Run();
665 wait_for_thread(thread
, NULL
);
667 const BHttpResult
& result
= dynamic_cast<const BHttpResult
&>(
670 int32 statusCode
= result
.StatusCode();
671 if (statusCode
!= 200) {
672 printf("Response code: %" B_PRId32
"\n", statusCode
);
676 jsonString
.SetTo(static_cast<const char*>(replyData
.Buffer()),
677 replyData
.BufferLength());
678 if (jsonString
.Length() == 0)
681 status_t status
= BJson::Parse(jsonString
, reply
);
682 if (Logger::IsTraceEnabled() && status
== B_BAD_DATA
) {
683 printf("Parser choked on JSON:\n%s\n", jsonString
.String());