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