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