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