1 /* 2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2016-2022, 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 /*! JSON-RPC invocations return a response. The response may be either 745 a result or it may be an error depending on the response structure. 746 If it is an error then there may be additional detail that is the 747 error code and message. This method will extract the error code 748 from the response. This method will return 0 if the payload does 749 not look like an error. 750 */ 751 752 int32 753 WebAppInterface::ErrorCodeFromResponse(BMessage& responseEnvelopeMessage) 754 { 755 BMessage error; 756 double code; 757 758 if (responseEnvelopeMessage.FindMessage("error", &error) == B_OK 759 && error.FindDouble("code", &code) == B_OK) { 760 return (int32) code; 761 } 762 763 return 0; 764 } 765 766 767 // #pragma mark - private 768 769 770 status_t 771 WebAppInterface::_SendJsonRequest(const char* urlPathComponents, 772 BPositionIO* requestData, size_t requestDataSize, uint32 flags, 773 BMessage& reply) const 774 { 775 return _SendJsonRequest(urlPathComponents, fCredentials, requestData, 776 requestDataSize, flags, reply); 777 } 778 779 780 status_t 781 WebAppInterface::_SendJsonRequest(const char* urlPathComponents, 782 UserCredentials credentials, BPositionIO* requestData, 783 size_t requestDataSize, uint32 flags, BMessage& reply) const 784 { 785 if (requestDataSize == 0) { 786 HDINFO("%s; empty request payload", PROTOCOL_NAME); 787 return B_ERROR; 788 } 789 790 if (!ServerHelper::IsNetworkAvailable()) { 791 HDDEBUG("%s; dropping request to ...[%s] as network is not" 792 " available", PROTOCOL_NAME, urlPathComponents); 793 delete requestData; 794 return HD_NETWORK_INACCESSIBLE; 795 } 796 797 if (ServerSettings::IsClientTooOld()) { 798 HDDEBUG("%s; dropping request to ...[%s] as client is too old", 799 PROTOCOL_NAME, urlPathComponents); 800 delete requestData; 801 return HD_CLIENT_TOO_OLD; 802 } 803 804 BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v2/") 805 << urlPathComponents); 806 HDDEBUG("%s; will make request to [%s]", PROTOCOL_NAME, 807 url.UrlString().String()); 808 809 // If the request payload is logged then it must be copied to local memory 810 // from the stream. This then requires that the request data is then 811 // delivered from memory. 812 813 if (Logger::IsTraceEnabled()) { 814 HDLOGPREFIX(LOG_LEVEL_TRACE) 815 printf("%s request; ", PROTOCOL_NAME); 816 _LogPayload(requestData, requestDataSize); 817 printf("\n"); 818 } 819 820 ProtocolListener listener; 821 BUrlContext context; 822 823 BHttpHeaders headers; 824 headers.AddHeader("Content-Type", "application/json"); 825 headers.AddHeader("Accept", "application/json"); 826 ServerSettings::AugmentHeaders(headers); 827 828 BHttpRequest* request = make_http_request(url, NULL, &listener, &context); 829 ObjectDeleter<BHttpRequest> _(request); 830 if (request == NULL) 831 return B_ERROR; 832 request->SetMethod(B_HTTP_POST); 833 request->SetHeaders(headers); 834 835 // Authentication via Basic Authentication 836 // The other way would be to obtain a token and then use the Token Bearer 837 // header. 838 if (((flags & NEEDS_AUTHORIZATION) != 0) && credentials.IsValid()) { 839 BHttpAuthentication authentication(credentials.Nickname(), 840 credentials.PasswordClear()); 841 authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC); 842 context.AddAuthentication(url, authentication); 843 } 844 845 request->AdoptInputData(requestData, requestDataSize); 846 847 BMallocIO replyData; 848 request->SetOutput(&replyData); 849 850 thread_id thread = request->Run(); 851 wait_for_thread(thread, NULL); 852 853 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 854 request->Result()); 855 856 int32 statusCode = result.StatusCode(); 857 858 HDDEBUG("%s; did receive http-status [%" B_PRId32 "] from [%s]", 859 PROTOCOL_NAME, statusCode, url.UrlString().String()); 860 861 switch (statusCode) { 862 case B_HTTP_STATUS_OK: 863 break; 864 865 case B_HTTP_STATUS_PRECONDITION_FAILED: 866 ServerHelper::NotifyClientTooOld(result.Headers()); 867 return HD_CLIENT_TOO_OLD; 868 869 default: 870 HDERROR("%s; request to endpoint [.../%s] failed with http " 871 "status [%" B_PRId32 "]\n", PROTOCOL_NAME, urlPathComponents, 872 statusCode); 873 return B_ERROR; 874 } 875 876 replyData.Seek(0, SEEK_SET); 877 878 if (Logger::IsTraceEnabled()) { 879 HDLOGPREFIX(LOG_LEVEL_TRACE) 880 printf("%s; response; ", PROTOCOL_NAME); 881 _LogPayload(&replyData, replyData.BufferLength()); 882 printf("\n"); 883 } 884 885 BJsonMessageWriter jsonMessageWriter(reply); 886 BJson::Parse(&replyData, &jsonMessageWriter); 887 status_t status = jsonMessageWriter.ErrorStatus(); 888 889 if (Logger::IsTraceEnabled() && status == B_BAD_DATA) { 890 BString resultString(static_cast<const char *>(replyData.Buffer()), 891 replyData.BufferLength()); 892 HDERROR("Parser choked on JSON:\n%s", resultString.String()); 893 } 894 return status; 895 } 896 897 898 status_t 899 WebAppInterface::_SendJsonRequest(const char* urlPathComponents, 900 const BString& jsonString, uint32 flags, BMessage& reply) const 901 { 902 // gets 'adopted' by the subsequent http request. 903 BMemoryIO* data = new BMemoryIO(jsonString.String(), 904 jsonString.Length() - 1); 905 906 return _SendJsonRequest(urlPathComponents, data, jsonString.Length() - 1, 907 flags, reply); 908 } 909 910 911 status_t 912 WebAppInterface::_SendRawGetRequest(const BString urlPathComponents, 913 BDataIO* stream) 914 { 915 BUrl url = ServerSettings::CreateFullUrl(urlPathComponents); 916 917 HDDEBUG("http-get; will make request to [%s]", 918 url.UrlString().String()); 919 920 ProtocolListener listener; 921 922 BHttpHeaders headers; 923 ServerSettings::AugmentHeaders(headers); 924 925 BHttpRequest *request = make_http_request(url, stream, &listener); 926 ObjectDeleter<BHttpRequest> _(request); 927 if (request == NULL) 928 return B_ERROR; 929 request->SetMethod(B_HTTP_GET); 930 request->SetHeaders(headers); 931 932 thread_id thread = request->Run(); 933 wait_for_thread(thread, NULL); 934 935 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 936 request->Result()); 937 938 int32 statusCode = result.StatusCode(); 939 940 HDDEBUG("http-get; did receive http-status [%" B_PRId32 "] from [%s]", 941 statusCode, url.UrlString().String()); 942 943 if (statusCode == 200) 944 return B_OK; 945 946 HDERROR("failed to get data from '%s': %" B_PRIi32 "", 947 url.UrlString().String(), statusCode); 948 return B_ERROR; 949 } 950 951 952 void 953 WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size) 954 { 955 off_t requestDataOffset = requestData->Position(); 956 char buffer[LOG_PAYLOAD_LIMIT]; 957 958 if (size > LOG_PAYLOAD_LIMIT) 959 size = LOG_PAYLOAD_LIMIT; 960 961 if (B_OK != requestData->ReadExactly(buffer, size)) { 962 printf("%s; error logging payload", PROTOCOL_NAME); 963 } else { 964 for (uint32 i = 0; i < size; i++) { 965 bool esc = buffer[i] > 126 || 966 (buffer[i] < 0x20 && buffer[i] != 0x0a); 967 968 if (esc) 969 printf("\\u%02x", buffer[i]); 970 else 971 putchar(buffer[i]); 972 } 973 974 if (size == LOG_PAYLOAD_LIMIT) 975 printf("...(continues)"); 976 } 977 978 requestData->Seek(requestDataOffset, SEEK_SET); 979 } 980 981 982 /*! This will get the position of the data to get the length an then sets the 983 offset to zero so that it can be re-read for reading the payload in to log 984 or send. 985 */ 986 987 off_t 988 WebAppInterface::_LengthAndSeekToZero(BPositionIO* data) 989 { 990 off_t dataSize = data->Position(); 991 data->Seek(0, SEEK_SET); 992 return dataSize; 993 } 994