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