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