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