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 43 class JsonBuilder { 44 public: 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 165 private: 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 190 private: 191 BString fString; 192 bool fInList; 193 }; 194 195 196 class ProtocolListener : public BUrlProtocolListener { 197 public: 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 258 private: 259 BDataIO* fDownloadIO; 260 bool fTraceLogging; 261 }; 262 263 264 int 265 WebAppInterface::fRequestIndex = 0; 266 267 268 enum { 269 NEEDS_AUTHORIZATION = 1 << 0, 270 }; 271 272 273 WebAppInterface::WebAppInterface() 274 { 275 } 276 277 278 WebAppInterface::WebAppInterface(const WebAppInterface& other) 279 : 280 fCredentials(other.fCredentials) 281 { 282 } 283 284 285 WebAppInterface::~WebAppInterface() 286 { 287 } 288 289 290 WebAppInterface& 291 WebAppInterface::operator=(const WebAppInterface& other) 292 { 293 if (this == &other) 294 return *this; 295 fCredentials = other.fCredentials; 296 return *this; 297 } 298 299 300 void 301 WebAppInterface::SetAuthorization(const UserCredentials& value) 302 { 303 fCredentials = value; 304 } 305 306 307 const BString& 308 WebAppInterface::Nickname() const 309 { 310 return fCredentials.Nickname(); 311 } 312 313 314 status_t 315 WebAppInterface::GetChangelog(const BString& packageName, BMessage& message) 316 { 317 BString jsonString = JsonBuilder() 318 .AddValue("jsonrpc", "2.0") 319 .AddValue("id", ++fRequestIndex) 320 .AddValue("method", "getPkgChangelog") 321 .AddArray("params") 322 .AddObject() 323 .AddValue("pkgName", packageName) 324 .EndObject() 325 .EndArray() 326 .End(); 327 328 return _SendJsonRequest("pkg", jsonString, 0, message); 329 } 330 331 332 status_t 333 WebAppInterface::RetreiveUserRatingsForPackageForDisplay( 334 const BString& packageName, const BString& webAppRepositoryCode, 335 int resultOffset, int maxResults, BMessage& message) 336 { 337 // BHttpRequest later takes ownership of this. 338 BMallocIO* requestEnvelopeData = new BMallocIO(); 339 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 340 341 requestEnvelopeWriter.WriteObjectStart(); 342 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 343 "searchUserRatings"); 344 requestEnvelopeWriter.WriteObjectName("params"); 345 requestEnvelopeWriter.WriteArrayStart(); 346 requestEnvelopeWriter.WriteObjectStart(); 347 348 requestEnvelopeWriter.WriteObjectName("pkgName"); 349 requestEnvelopeWriter.WriteString(packageName.String()); 350 requestEnvelopeWriter.WriteObjectName("offset"); 351 requestEnvelopeWriter.WriteInteger(resultOffset); 352 requestEnvelopeWriter.WriteObjectName("limit"); 353 requestEnvelopeWriter.WriteInteger(maxResults); 354 355 if (!webAppRepositoryCode.IsEmpty()) { 356 requestEnvelopeWriter.WriteObjectName("repositoryCode"); 357 requestEnvelopeWriter.WriteString(webAppRepositoryCode); 358 } 359 360 requestEnvelopeWriter.WriteObjectEnd(); 361 requestEnvelopeWriter.WriteArrayEnd(); 362 requestEnvelopeWriter.WriteObjectEnd(); 363 364 return _SendJsonRequest("userrating", requestEnvelopeData, 365 _LengthAndSeekToZero(requestEnvelopeData), 0, 366 message); 367 } 368 369 370 status_t 371 WebAppInterface::RetreiveUserRatingForPackageAndVersionByUser( 372 const BString& packageName, const BPackageVersion& version, 373 const BString& architecture, const BString &repositoryCode, 374 const BString& userNickname, BMessage& message) 375 { 376 // BHttpRequest later takes ownership of this. 377 BMallocIO* requestEnvelopeData = new BMallocIO(); 378 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 379 380 requestEnvelopeWriter.WriteObjectStart(); 381 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 382 "getUserRatingByUserAndPkgVersion"); 383 requestEnvelopeWriter.WriteObjectName("params"); 384 requestEnvelopeWriter.WriteArrayStart(); 385 386 requestEnvelopeWriter.WriteObjectStart(); 387 388 requestEnvelopeWriter.WriteObjectName("userNickname"); 389 requestEnvelopeWriter.WriteString(userNickname.String()); 390 requestEnvelopeWriter.WriteObjectName("pkgName"); 391 requestEnvelopeWriter.WriteString(packageName.String()); 392 requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode"); 393 requestEnvelopeWriter.WriteString(architecture.String()); 394 requestEnvelopeWriter.WriteObjectName("repositoryCode"); 395 requestEnvelopeWriter.WriteString(repositoryCode.String()); 396 397 if (version.Major().Length() > 0) { 398 requestEnvelopeWriter.WriteObjectName("pkgVersionMajor"); 399 requestEnvelopeWriter.WriteString(version.Major().String()); 400 } 401 402 if (version.Minor().Length() > 0) { 403 requestEnvelopeWriter.WriteObjectName("pkgVersionMinor"); 404 requestEnvelopeWriter.WriteString(version.Minor().String()); 405 } 406 407 if (version.Micro().Length() > 0) { 408 requestEnvelopeWriter.WriteObjectName("pkgVersionMicro"); 409 requestEnvelopeWriter.WriteString(version.Micro().String()); 410 } 411 412 if (version.PreRelease().Length() > 0) { 413 requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease"); 414 requestEnvelopeWriter.WriteString(version.PreRelease().String()); 415 } 416 417 if (version.Revision() != 0) { 418 requestEnvelopeWriter.WriteObjectName("pkgVersionRevision"); 419 requestEnvelopeWriter.WriteInteger(version.Revision()); 420 } 421 422 requestEnvelopeWriter.WriteObjectEnd(); 423 requestEnvelopeWriter.WriteArrayEnd(); 424 requestEnvelopeWriter.WriteObjectEnd(); 425 426 return _SendJsonRequest("userrating", requestEnvelopeData, 427 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 428 message); 429 } 430 431 432 /*! This method will fill out the supplied UserDetail object with information 433 about the user that is supplied in the credentials. Importantly it will 434 also authenticate the request with the details of the credentials and will 435 not use the credentials that are configured in 'fCredentials'. 436 */ 437 438 status_t 439 WebAppInterface::RetrieveUserDetailForCredentials( 440 const UserCredentials& credentials, UserDetail& userDetail) 441 { 442 if (!credentials.IsValid()) { 443 debugger("the credentials supplied are invalid so it is not possible " 444 "to obtain the user detail"); 445 } 446 447 // BHttpRequest later takes ownership of this. 448 BMallocIO* requestEnvelopeData = new BMallocIO(); 449 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 450 451 requestEnvelopeWriter.WriteObjectStart(); 452 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, "getUser"); 453 requestEnvelopeWriter.WriteObjectName("params"); 454 requestEnvelopeWriter.WriteArrayStart(); 455 requestEnvelopeWriter.WriteObjectStart(); 456 requestEnvelopeWriter.WriteObjectName("nickname"); 457 requestEnvelopeWriter.WriteString(credentials.Nickname().String()); 458 requestEnvelopeWriter.WriteObjectEnd(); 459 requestEnvelopeWriter.WriteArrayEnd(); 460 requestEnvelopeWriter.WriteObjectEnd(); 461 462 BMessage responseEnvelopeMessage; 463 status_t result = _SendJsonRequest("user", credentials, requestEnvelopeData, 464 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 465 responseEnvelopeMessage); 466 // note that the credentials used here are passed in as args. 467 468 if (result == B_OK) 469 result = _UnpackUserDetails(responseEnvelopeMessage, userDetail); 470 471 return result; 472 } 473 474 475 /*! This method will return the credentials for the currently authenticated 476 user. 477 */ 478 479 status_t 480 WebAppInterface::RetrieveCurrentUserDetail(UserDetail& userDetail) 481 { 482 return RetrieveUserDetailForCredentials(fCredentials, userDetail); 483 } 484 485 486 /*! When the user requests user detail, the server sends back an envelope of 487 response data. This method will unpack the data into a model object. 488 \return Not B_OK if something went wrong. 489 */ 490 491 /*static*/ status_t 492 WebAppInterface::_UnpackUserDetails(BMessage& responseEnvelopeMessage, 493 UserDetail& userDetail) 494 { 495 BMessage resultMessage; 496 status_t result = responseEnvelopeMessage.FindMessage( 497 "result", &resultMessage); 498 499 if (result != B_OK) { 500 fprintf(stderr, "bad response envelope missing 'result' entry\n"); 501 return result; 502 } 503 504 BString nickname; 505 result = resultMessage.FindString("nickname", &nickname); 506 userDetail.SetNickname(nickname); 507 508 BMessage agreementMessage; 509 if (resultMessage.FindMessage("userUsageConditionsAgreement", 510 &agreementMessage) == B_OK) { 511 BString code; 512 BDateTime agreedToTimestamp; 513 BString userUsageConditionsCode; 514 UserUsageConditionsAgreement agreement = userDetail.Agreement(); 515 bool isLatest; 516 517 if (agreementMessage.FindString("userUsageConditionsCode", 518 &userUsageConditionsCode) == B_OK) { 519 agreement.SetCode(userUsageConditionsCode); 520 } 521 522 double timestampAgreedMillis; 523 if (agreementMessage.FindDouble("timestampAgreed", 524 ×tampAgreedMillis) == B_OK) { 525 agreement.SetTimestampAgreed((uint64) timestampAgreedMillis); 526 } 527 528 if (agreementMessage.FindBool("isLatest", &isLatest) 529 == B_OK) { 530 agreement.SetIsLatest(isLatest); 531 } 532 533 userDetail.SetAgreement(agreement); 534 } 535 536 return result; 537 } 538 539 540 /*! \brief Returns data relating to the user usage conditions 541 542 \param code defines the version of the data to return or if empty then the 543 latest is returned. 544 545 This method will go to the server and get details relating to the user usage 546 conditions. It does this in two API calls; first gets the details (the 547 minimum age) and in the second call, the text of the conditions is returned. 548 */ 549 550 status_t 551 WebAppInterface::RetrieveUserUsageConditions(const BString& code, 552 UserUsageConditions& conditions) 553 { 554 BMessage responseEnvelopeMessage; 555 status_t result = _RetrieveUserUsageConditionsMeta(code, 556 responseEnvelopeMessage); 557 558 if (result != B_OK) 559 return result; 560 561 BMessage resultMessage; 562 if (responseEnvelopeMessage.FindMessage("result", &resultMessage) != B_OK) { 563 fprintf(stderr, "bad response envelope missing 'result' entry\n"); 564 return B_BAD_DATA; 565 } 566 567 BString metaDataCode; 568 double metaDataMinimumAge; 569 BString copyMarkdown; 570 571 if ( (resultMessage.FindString("code", &metaDataCode) != B_OK) 572 || (resultMessage.FindDouble( 573 "minimumAge", &metaDataMinimumAge) != B_OK) ) { 574 printf("unexpected response from server with missing user usage " 575 "conditions data\n"); 576 return B_BAD_DATA; 577 } 578 579 BMallocIO* copyMarkdownData = new BMallocIO(); 580 result = _RetrieveUserUsageConditionsCopy(metaDataCode, copyMarkdownData); 581 582 if (result != B_OK) 583 return result; 584 585 conditions.SetCode(metaDataCode); 586 conditions.SetMinimumAge(metaDataMinimumAge); 587 conditions.SetCopyMarkdown( 588 BString(static_cast<const char*>(copyMarkdownData->Buffer()), 589 copyMarkdownData->BufferLength())); 590 591 return B_OK; 592 } 593 594 595 status_t 596 WebAppInterface::_RetrieveUserUsageConditionsMeta(const BString& code, 597 BMessage& message) 598 { 599 BMallocIO* requestEnvelopeData = new BMallocIO(); 600 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 601 602 requestEnvelopeWriter.WriteObjectStart(); 603 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 604 "getUserUsageConditions"); 605 requestEnvelopeWriter.WriteObjectName("params"); 606 requestEnvelopeWriter.WriteArrayStart(); 607 608 requestEnvelopeWriter.WriteObjectStart(); 609 610 if (!code.IsEmpty()) { 611 requestEnvelopeWriter.WriteObjectName("code"); 612 requestEnvelopeWriter.WriteString(code.String()); 613 } 614 615 requestEnvelopeWriter.WriteObjectEnd(); 616 requestEnvelopeWriter.WriteArrayEnd(); 617 requestEnvelopeWriter.WriteObjectEnd(); 618 619 // now fetch this information into an object. 620 621 return _SendJsonRequest("user", requestEnvelopeData, 622 _LengthAndSeekToZero(requestEnvelopeData), 0, message); 623 } 624 625 626 status_t 627 WebAppInterface::_RetrieveUserUsageConditionsCopy(const BString& code, 628 BDataIO* stream) 629 { 630 return _SendRawGetRequest( 631 BString("/__user/usageconditions/") << code << "/document.md", 632 stream); 633 } 634 635 636 status_t 637 WebAppInterface::CreateUserRating(const BString& packageName, 638 const BPackageVersion& version, 639 const BString& architecture, const BString& repositoryCode, 640 const BString& languageCode, const BString& comment, 641 const BString& stability, int rating, BMessage& message) 642 { 643 // BHttpRequest later takes ownership of this. 644 BMallocIO* requestEnvelopeData = new BMallocIO(); 645 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 646 647 requestEnvelopeWriter.WriteObjectStart(); 648 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 649 "createUserRating"); 650 requestEnvelopeWriter.WriteObjectName("params"); 651 requestEnvelopeWriter.WriteArrayStart(); 652 653 requestEnvelopeWriter.WriteObjectStart(); 654 requestEnvelopeWriter.WriteObjectName("pkgName"); 655 requestEnvelopeWriter.WriteString(packageName.String()); 656 requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode"); 657 requestEnvelopeWriter.WriteString(architecture.String()); 658 requestEnvelopeWriter.WriteObjectName("repositoryCode"); 659 requestEnvelopeWriter.WriteString(repositoryCode.String()); 660 requestEnvelopeWriter.WriteObjectName("naturalLanguageCode"); 661 requestEnvelopeWriter.WriteString(languageCode.String()); 662 requestEnvelopeWriter.WriteObjectName("pkgVersionType"); 663 requestEnvelopeWriter.WriteString("SPECIFIC"); 664 requestEnvelopeWriter.WriteObjectName("userNickname"); 665 requestEnvelopeWriter.WriteString(fCredentials.Nickname()); 666 667 if (!version.Major().IsEmpty()) { 668 requestEnvelopeWriter.WriteObjectName("pkgVersionMajor"); 669 requestEnvelopeWriter.WriteString(version.Major()); 670 } 671 672 if (!version.Minor().IsEmpty()) { 673 requestEnvelopeWriter.WriteObjectName("pkgVersionMinor"); 674 requestEnvelopeWriter.WriteString(version.Minor()); 675 } 676 677 if (!version.Micro().IsEmpty()) { 678 requestEnvelopeWriter.WriteObjectName("pkgVersionMicro"); 679 requestEnvelopeWriter.WriteString(version.Micro()); 680 } 681 682 if (!version.PreRelease().IsEmpty()) { 683 requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease"); 684 requestEnvelopeWriter.WriteString(version.PreRelease()); 685 } 686 687 if (version.Revision() != 0) { 688 requestEnvelopeWriter.WriteObjectName("pkgVersionRevision"); 689 requestEnvelopeWriter.WriteInteger(version.Revision()); 690 } 691 692 if (rating > 0.0f) { 693 requestEnvelopeWriter.WriteObjectName("rating"); 694 requestEnvelopeWriter.WriteInteger(rating); 695 } 696 697 if (stability.Length() > 0) { 698 requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode"); 699 requestEnvelopeWriter.WriteString(stability); 700 } 701 702 if (comment.Length() > 0) { 703 requestEnvelopeWriter.WriteObjectName("comment"); 704 requestEnvelopeWriter.WriteString(comment.String()); 705 } 706 707 requestEnvelopeWriter.WriteObjectEnd(); 708 requestEnvelopeWriter.WriteArrayEnd(); 709 requestEnvelopeWriter.WriteObjectEnd(); 710 711 return _SendJsonRequest("userrating", requestEnvelopeData, 712 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 713 message); 714 } 715 716 717 status_t 718 WebAppInterface::UpdateUserRating(const BString& ratingID, 719 const BString& languageCode, const BString& comment, 720 const BString& stability, int rating, bool active, BMessage& message) 721 { 722 // BHttpRequest later takes ownership of this. 723 BMallocIO* requestEnvelopeData = new BMallocIO(); 724 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 725 726 requestEnvelopeWriter.WriteObjectStart(); 727 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 728 "updateUserRating"); 729 730 requestEnvelopeWriter.WriteObjectName("params"); 731 requestEnvelopeWriter.WriteArrayStart(); 732 733 requestEnvelopeWriter.WriteObjectStart(); 734 735 requestEnvelopeWriter.WriteObjectName("code"); 736 requestEnvelopeWriter.WriteString(ratingID.String()); 737 requestEnvelopeWriter.WriteObjectName("naturalLanguageCode"); 738 requestEnvelopeWriter.WriteString(languageCode.String()); 739 requestEnvelopeWriter.WriteObjectName("active"); 740 requestEnvelopeWriter.WriteBoolean(active); 741 742 requestEnvelopeWriter.WriteObjectName("filter"); 743 requestEnvelopeWriter.WriteArrayStart(); 744 requestEnvelopeWriter.WriteString("ACTIVE"); 745 requestEnvelopeWriter.WriteString("NATURALLANGUAGE"); 746 requestEnvelopeWriter.WriteString("USERRATINGSTABILITY"); 747 requestEnvelopeWriter.WriteString("COMMENT"); 748 requestEnvelopeWriter.WriteString("RATING"); 749 requestEnvelopeWriter.WriteArrayEnd(); 750 751 if (rating >= 0) { 752 requestEnvelopeWriter.WriteObjectName("rating"); 753 requestEnvelopeWriter.WriteInteger(rating); 754 } 755 756 if (stability.Length() > 0) { 757 requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode"); 758 requestEnvelopeWriter.WriteString(stability); 759 } 760 761 if (comment.Length() > 0) { 762 requestEnvelopeWriter.WriteObjectName("comment"); 763 requestEnvelopeWriter.WriteString(comment); 764 } 765 766 requestEnvelopeWriter.WriteObjectEnd(); 767 requestEnvelopeWriter.WriteArrayEnd(); 768 requestEnvelopeWriter.WriteObjectEnd(); 769 770 return _SendJsonRequest("userrating", requestEnvelopeData, 771 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 772 message); 773 } 774 775 776 status_t 777 WebAppInterface::RetrieveScreenshot(const BString& code, 778 int32 width, int32 height, BDataIO* stream) 779 { 780 return _SendRawGetRequest( 781 BString("/__pkgscreenshot/") << code << ".png" << "?tw=" 782 << width << "&th=" << height, stream); 783 } 784 785 786 status_t 787 WebAppInterface::RequestCaptcha(BMessage& message) 788 { 789 BString jsonString = JsonBuilder() 790 .AddValue("jsonrpc", "2.0") 791 .AddValue("id", ++fRequestIndex) 792 .AddValue("method", "generateCaptcha") 793 .AddArray("params") 794 .AddObject() 795 .EndObject() 796 .EndArray() 797 .End(); 798 799 return _SendJsonRequest("captcha", jsonString, 0, message); 800 } 801 802 803 status_t 804 WebAppInterface::CreateUser(const BString& nickName, 805 const BString& passwordClear, const BString& email, 806 const BString& captchaToken, const BString& captchaResponse, 807 const BString& languageCode, const BString& userUsageConditionsCode, 808 BMessage& message) 809 { 810 // BHttpRequest later takes ownership of this. 811 BMallocIO* requestEnvelopeData = new BMallocIO(); 812 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 813 814 requestEnvelopeWriter.WriteObjectStart(); 815 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, "createUser"); 816 requestEnvelopeWriter.WriteObjectName("params"); 817 requestEnvelopeWriter.WriteArrayStart(); 818 819 requestEnvelopeWriter.WriteObjectStart(); 820 821 requestEnvelopeWriter.WriteObjectName("nickname"); 822 requestEnvelopeWriter.WriteString(nickName.String()); 823 requestEnvelopeWriter.WriteObjectName("passwordClear"); 824 requestEnvelopeWriter.WriteString(passwordClear.String()); 825 requestEnvelopeWriter.WriteObjectName("captchaToken"); 826 requestEnvelopeWriter.WriteString(captchaToken.String()); 827 requestEnvelopeWriter.WriteObjectName("captchaResponse"); 828 requestEnvelopeWriter.WriteString(captchaResponse.String()); 829 requestEnvelopeWriter.WriteObjectName("naturalLanguageCode"); 830 requestEnvelopeWriter.WriteString(languageCode.String()); 831 requestEnvelopeWriter.WriteObjectName("userUsageConditionsCode"); 832 requestEnvelopeWriter.WriteString(userUsageConditionsCode.String()); 833 834 if (!email.IsEmpty()) { 835 requestEnvelopeWriter.WriteObjectName("email"); 836 requestEnvelopeWriter.WriteString(email.String()); 837 } 838 839 requestEnvelopeWriter.WriteObjectEnd(); 840 requestEnvelopeWriter.WriteArrayEnd(); 841 requestEnvelopeWriter.WriteObjectEnd(); 842 843 return _SendJsonRequest("user", requestEnvelopeData, 844 _LengthAndSeekToZero(requestEnvelopeData), 0, message); 845 } 846 847 848 status_t 849 WebAppInterface::AuthenticateUser(const BString& nickName, 850 const BString& passwordClear, BMessage& message) 851 { 852 BString jsonString = JsonBuilder() 853 .AddValue("jsonrpc", "2.0") 854 .AddValue("id", ++fRequestIndex) 855 .AddValue("method", "authenticateUser") 856 .AddArray("params") 857 .AddObject() 858 .AddValue("nickname", nickName) 859 .AddValue("passwordClear", passwordClear) 860 .EndObject() 861 .EndArray() 862 .End(); 863 864 return _SendJsonRequest("user", jsonString, 0, message); 865 } 866 867 868 /*! JSON-RPC invocations return a response. The response may be either 869 a result or it may be an error depending on the response structure. 870 If it is an error then there may be additional detail that is the 871 error code and message. This method will extract the error code 872 from the response. This method will return 0 if the payload does 873 not look like an error. 874 */ 875 876 int32 877 WebAppInterface::ErrorCodeFromResponse(BMessage& response) 878 { 879 BMessage error; 880 double code; 881 882 if (response.FindMessage("error", &error) == B_OK 883 && error.FindDouble("code", &code) == B_OK) { 884 return (int32) code; 885 } 886 887 return 0; 888 } 889 890 891 // #pragma mark - private 892 893 894 void 895 WebAppInterface::_WriteStandardJsonRpcEnvelopeValues(BJsonWriter& writer, 896 const char* methodName) 897 { 898 writer.WriteObjectName("jsonrpc"); 899 writer.WriteString("2.0"); 900 writer.WriteObjectName("id"); 901 writer.WriteInteger(++fRequestIndex); 902 writer.WriteObjectName("method"); 903 writer.WriteString(methodName); 904 } 905 906 907 status_t 908 WebAppInterface::_SendJsonRequest(const char* domain, BPositionIO* requestData, 909 size_t requestDataSize, uint32 flags, BMessage& reply) const 910 { 911 return _SendJsonRequest(domain, fCredentials, requestData, requestDataSize, 912 flags, reply); 913 } 914 915 916 status_t 917 WebAppInterface::_SendJsonRequest(const char* domain, 918 UserCredentials credentials, BPositionIO* requestData, 919 size_t requestDataSize, uint32 flags, BMessage& reply) const 920 { 921 if (requestDataSize == 0) { 922 if (Logger::IsInfoEnabled()) 923 printf("jrpc; empty request payload\n"); 924 return B_ERROR; 925 } 926 927 if (!ServerHelper::IsNetworkAvailable()) { 928 if (Logger::IsDebugEnabled()) { 929 printf("jrpc; dropping request to ...[%s] as network is not " 930 "available\n", domain); 931 } 932 delete requestData; 933 return HD_NETWORK_INACCESSIBLE; 934 } 935 936 if (ServerSettings::IsClientTooOld()) { 937 if (Logger::IsDebugEnabled()) { 938 printf("jrpc; dropping request to ...[%s] as client is too " 939 "old\n", domain); 940 } 941 delete requestData; 942 return HD_CLIENT_TOO_OLD; 943 } 944 945 BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v1/") << domain); 946 bool isSecure = url.Protocol() == "https"; 947 948 if (Logger::IsDebugEnabled()) { 949 printf("jrpc; will make request to [%s]\n", 950 url.UrlString().String()); 951 } 952 953 // If the request payload is logged then it must be copied to local memory 954 // from the stream. This then requires that the request data is then 955 // delivered from memory. 956 957 if (Logger::IsTraceEnabled()) { 958 printf("jrpc request; "); 959 _LogPayload(requestData, requestDataSize); 960 printf("\n"); 961 } 962 963 ProtocolListener listener(Logger::IsTraceEnabled()); 964 BUrlContext context; 965 966 BHttpHeaders headers; 967 headers.AddHeader("Content-Type", "application/json"); 968 ServerSettings::AugmentHeaders(headers); 969 970 BHttpRequest request(url, isSecure, "HTTP", &listener, &context); 971 request.SetMethod(B_HTTP_POST); 972 request.SetHeaders(headers); 973 974 // Authentication via Basic Authentication 975 // The other way would be to obtain a token and then use the Token Bearer 976 // header. 977 if (((flags & NEEDS_AUTHORIZATION) != 0) && credentials.IsValid()) { 978 BHttpAuthentication authentication(credentials.Nickname(), 979 credentials.PasswordClear()); 980 authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC); 981 context.AddAuthentication(url, authentication); 982 } 983 984 request.AdoptInputData(requestData, requestDataSize); 985 986 BMallocIO replyData; 987 listener.SetDownloadIO(&replyData); 988 989 thread_id thread = request.Run(); 990 wait_for_thread(thread, NULL); 991 992 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 993 request.Result()); 994 995 int32 statusCode = result.StatusCode(); 996 997 if (Logger::IsDebugEnabled()) { 998 printf("jrpc; did receive http-status [%" B_PRId32 "] " 999 "from [%s]\n", statusCode, url.UrlString().String()); 1000 } 1001 1002 switch (statusCode) { 1003 case B_HTTP_STATUS_OK: 1004 break; 1005 1006 case B_HTTP_STATUS_PRECONDITION_FAILED: 1007 ServerHelper::NotifyClientTooOld(result.Headers()); 1008 return HD_CLIENT_TOO_OLD; 1009 1010 default: 1011 printf("jrpc request to endpoint [.../%s] failed with http " 1012 "status [%" B_PRId32 "]\n", domain, statusCode); 1013 return B_ERROR; 1014 } 1015 1016 replyData.Seek(0, SEEK_SET); 1017 1018 if (Logger::IsTraceEnabled()) { 1019 printf("jrpc response; "); 1020 _LogPayload(&replyData, replyData.BufferLength()); 1021 printf("\n"); 1022 } 1023 1024 BJsonMessageWriter jsonMessageWriter(reply); 1025 BJson::Parse(&replyData, &jsonMessageWriter); 1026 status_t status = jsonMessageWriter.ErrorStatus(); 1027 1028 if (Logger::IsTraceEnabled() && status == B_BAD_DATA) { 1029 BString resultString(static_cast<const char *>(replyData.Buffer()), 1030 replyData.BufferLength()); 1031 printf("Parser choked on JSON:\n%s\n", resultString.String()); 1032 } 1033 return status; 1034 } 1035 1036 1037 status_t 1038 WebAppInterface::_SendJsonRequest(const char* domain, const BString& jsonString, 1039 uint32 flags, BMessage& reply) const 1040 { 1041 // gets 'adopted' by the subsequent http request. 1042 BMemoryIO* data = new BMemoryIO(jsonString.String(), 1043 jsonString.Length() - 1); 1044 1045 return _SendJsonRequest(domain, data, jsonString.Length() - 1, flags, 1046 reply); 1047 } 1048 1049 1050 status_t 1051 WebAppInterface::_SendRawGetRequest(const BString urlPathComponents, 1052 BDataIO* stream) 1053 { 1054 BUrl url = ServerSettings::CreateFullUrl(urlPathComponents); 1055 bool isSecure = url.Protocol() == "https"; 1056 1057 ProtocolListener listener(Logger::IsTraceEnabled()); 1058 listener.SetDownloadIO(stream); 1059 1060 BHttpHeaders headers; 1061 ServerSettings::AugmentHeaders(headers); 1062 1063 BHttpRequest request(url, isSecure, "HTTP", &listener); 1064 request.SetMethod(B_HTTP_GET); 1065 request.SetHeaders(headers); 1066 1067 thread_id thread = request.Run(); 1068 wait_for_thread(thread, NULL); 1069 1070 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 1071 request.Result()); 1072 1073 int32 statusCode = result.StatusCode(); 1074 1075 if (statusCode == 200) 1076 return B_OK; 1077 1078 fprintf(stderr, "failed to get data from '%s': %" B_PRIi32 "\n", 1079 url.UrlString().String(), statusCode); 1080 return B_ERROR; 1081 } 1082 1083 1084 void 1085 WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size) 1086 { 1087 off_t requestDataOffset = requestData->Position(); 1088 char buffer[LOG_PAYLOAD_LIMIT]; 1089 1090 if (size > LOG_PAYLOAD_LIMIT) 1091 size = LOG_PAYLOAD_LIMIT; 1092 1093 if (B_OK != requestData->ReadExactly(buffer, size)) { 1094 printf("jrpc; error logging payload\n"); 1095 } else { 1096 for (uint32 i = 0; i < size; i++) { 1097 bool esc = buffer[i] > 126 || 1098 (buffer[i] < 0x20 && buffer[i] != 0x0a); 1099 1100 if (esc) 1101 printf("\\u%02x", buffer[i]); 1102 else 1103 putchar(buffer[i]); 1104 } 1105 1106 if (size == LOG_PAYLOAD_LIMIT) 1107 printf("...(continues)"); 1108 } 1109 1110 requestData->Seek(requestDataOffset, SEEK_SET); 1111 } 1112 1113 1114 /*! This will get the position of the data to get the length an then sets the 1115 offset to zero so that it can be re-read for reading the payload in to log 1116 or send. 1117 */ 1118 1119 off_t 1120 WebAppInterface::_LengthAndSeekToZero(BPositionIO* data) 1121 { 1122 off_t dataSize = data->Position(); 1123 data->Seek(0, SEEK_SET); 1124 return dataSize; 1125 } 1126