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