1 /* 2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2016-2018, 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 <stdio.h> 10 11 #include <AppFileInfo.h> 12 #include <Application.h> 13 #include <AutoDeleter.h> 14 #include <Autolock.h> 15 #include <File.h> 16 #include <HttpHeaders.h> 17 #include <HttpRequest.h> 18 #include <Json.h> 19 #include <JsonTextWriter.h> 20 #include <JsonMessageWriter.h> 21 #include <Message.h> 22 #include <Roster.h> 23 #include <Url.h> 24 #include <UrlContext.h> 25 #include <UrlProtocolListener.h> 26 #include <UrlProtocolRoster.h> 27 28 #include "AutoLocker.h" 29 #include "DataIOUtils.h" 30 #include "HaikuDepotConstants.h" 31 #include "List.h" 32 #include "Logger.h" 33 #include "PackageInfo.h" 34 #include "ServerSettings.h" 35 #include "ServerHelper.h" 36 37 38 #define BASEURL_DEFAULT "https://depot.haiku-os.org" 39 #define USERAGENT_FALLBACK_VERSION "0.0.0" 40 #define LOG_PAYLOAD_LIMIT 8192 41 42 43 class JsonBuilder { 44 public: 45 JsonBuilder() 46 : 47 fString("{"), 48 fInList(false) 49 { 50 } 51 52 JsonBuilder& AddObject() 53 { 54 fString << '{'; 55 fInList = false; 56 return *this; 57 } 58 59 JsonBuilder& AddObject(const char* name) 60 { 61 _StartName(name); 62 fString << '{'; 63 fInList = false; 64 return *this; 65 } 66 67 JsonBuilder& EndObject() 68 { 69 fString << '}'; 70 fInList = true; 71 return *this; 72 } 73 74 JsonBuilder& AddArray(const char* name) 75 { 76 _StartName(name); 77 fString << '['; 78 fInList = false; 79 return *this; 80 } 81 82 JsonBuilder& EndArray() 83 { 84 fString << ']'; 85 fInList = true; 86 return *this; 87 } 88 89 JsonBuilder& AddStrings(const StringList& strings) 90 { 91 for (int i = 0; i < strings.CountItems(); i++) 92 AddItem(strings.ItemAtFast(i)); 93 return *this; 94 } 95 96 JsonBuilder& AddItem(const char* item) 97 { 98 return AddItem(item, false); 99 } 100 101 JsonBuilder& AddItem(const char* item, bool nullIfEmpty) 102 { 103 if (item == NULL || (nullIfEmpty && strlen(item) == 0)) { 104 if (fInList) 105 fString << ",null"; 106 else 107 fString << "null"; 108 } else { 109 if (fInList) 110 fString << ",\""; 111 else 112 fString << '"'; 113 fString << _EscapeString(item); 114 fString << '"'; 115 } 116 fInList = true; 117 return *this; 118 } 119 120 JsonBuilder& AddValue(const char* name, const char* value) 121 { 122 return AddValue(name, value, false); 123 } 124 125 JsonBuilder& AddValue(const char* name, const char* value, 126 bool nullIfEmpty) 127 { 128 _StartName(name); 129 if (value == NULL || (nullIfEmpty && strlen(value) == 0)) { 130 fString << "null"; 131 } else { 132 fString << '"'; 133 fString << _EscapeString(value); 134 fString << '"'; 135 } 136 fInList = true; 137 return *this; 138 } 139 140 JsonBuilder& AddValue(const char* name, int value) 141 { 142 _StartName(name); 143 fString << value; 144 fInList = true; 145 return *this; 146 } 147 148 JsonBuilder& AddValue(const char* name, bool value) 149 { 150 _StartName(name); 151 if (value) 152 fString << "true"; 153 else 154 fString << "false"; 155 fInList = true; 156 return *this; 157 } 158 159 const BString& End() 160 { 161 fString << "}\n"; 162 return fString; 163 } 164 165 private: 166 void _StartName(const char* name) 167 { 168 if (fInList) 169 fString << ",\""; 170 else 171 fString << '"'; 172 fString << _EscapeString(name); 173 fString << "\":"; 174 } 175 176 BString _EscapeString(const char* original) const 177 { 178 BString string(original); 179 string.ReplaceAll("\\", "\\\\"); 180 string.ReplaceAll("\"", "\\\""); 181 string.ReplaceAll("/", "\\/"); 182 string.ReplaceAll("\b", "\\b"); 183 string.ReplaceAll("\f", "\\f"); 184 string.ReplaceAll("\n", "\\n"); 185 string.ReplaceAll("\r", "\\r"); 186 string.ReplaceAll("\t", "\\t"); 187 return string; 188 } 189 190 private: 191 BString fString; 192 bool fInList; 193 }; 194 195 196 class ProtocolListener : public BUrlProtocolListener { 197 public: 198 ProtocolListener(bool traceLogging) 199 : 200 fDownloadIO(NULL), 201 fTraceLogging(traceLogging) 202 { 203 } 204 205 virtual ~ProtocolListener() 206 { 207 } 208 209 virtual void ConnectionOpened(BUrlRequest* caller) 210 { 211 } 212 213 virtual void HostnameResolved(BUrlRequest* caller, const char* ip) 214 { 215 } 216 217 virtual void ResponseStarted(BUrlRequest* caller) 218 { 219 } 220 221 virtual void HeadersReceived(BUrlRequest* caller, const BUrlResult& result) 222 { 223 } 224 225 virtual void DataReceived(BUrlRequest* caller, const char* data, 226 off_t position, ssize_t size) 227 { 228 if (fDownloadIO != NULL) 229 fDownloadIO->Write(data, size); 230 } 231 232 virtual void DownloadProgress(BUrlRequest* caller, ssize_t bytesReceived, 233 ssize_t bytesTotal) 234 { 235 } 236 237 virtual void UploadProgress(BUrlRequest* caller, ssize_t bytesSent, 238 ssize_t bytesTotal) 239 { 240 } 241 242 virtual void RequestCompleted(BUrlRequest* caller, bool success) 243 { 244 } 245 246 virtual void DebugMessage(BUrlRequest* caller, 247 BUrlProtocolDebugMessage type, const char* text) 248 { 249 if (fTraceLogging) 250 printf("jrpc: %s\n", text); 251 } 252 253 void SetDownloadIO(BDataIO* downloadIO) 254 { 255 fDownloadIO = downloadIO; 256 } 257 258 private: 259 BDataIO* fDownloadIO; 260 bool fTraceLogging; 261 }; 262 263 264 int 265 WebAppInterface::fRequestIndex = 0; 266 267 268 enum { 269 NEEDS_AUTHORIZATION = 1 << 0, 270 }; 271 272 273 WebAppInterface::WebAppInterface() 274 : 275 fLanguage("en") 276 { 277 } 278 279 280 WebAppInterface::WebAppInterface(const WebAppInterface& other) 281 : 282 fUsername(other.fUsername), 283 fPassword(other.fPassword), 284 fLanguage(other.fLanguage) 285 { 286 } 287 288 289 WebAppInterface::~WebAppInterface() 290 { 291 } 292 293 294 WebAppInterface& 295 WebAppInterface::operator=(const WebAppInterface& other) 296 { 297 if (this == &other) 298 return *this; 299 300 fUsername = other.fUsername; 301 fPassword = other.fPassword; 302 fLanguage = other.fLanguage; 303 304 return *this; 305 } 306 307 308 void 309 WebAppInterface::SetAuthorization(const BString& username, 310 const BString& password) 311 { 312 fUsername = username; 313 fPassword = password; 314 } 315 316 317 void 318 WebAppInterface::SetPreferredLanguage(const BString& language) 319 { 320 fLanguage = language; 321 } 322 323 324 status_t 325 WebAppInterface::GetChangelog(const BString& packageName, BMessage& message) 326 { 327 BString jsonString = JsonBuilder() 328 .AddValue("jsonrpc", "2.0") 329 .AddValue("id", ++fRequestIndex) 330 .AddValue("method", "getPkgChangelog") 331 .AddArray("params") 332 .AddObject() 333 .AddValue("pkgName", packageName) 334 .EndObject() 335 .EndArray() 336 .End(); 337 338 return _SendJsonRequest("pkg", jsonString, 0, message); 339 } 340 341 342 status_t 343 WebAppInterface::RetrieveUserRatings(const BString& packageName, 344 const BString& architecture, int resultOffset, int maxResults, 345 BMessage& message) 346 { 347 BString jsonString = JsonBuilder() 348 .AddValue("jsonrpc", "2.0") 349 .AddValue("id", ++fRequestIndex) 350 .AddValue("method", "searchUserRatings") 351 .AddArray("params") 352 .AddObject() 353 .AddValue("pkgName", packageName) 354 .AddValue("pkgVersionArchitectureCode", architecture) 355 .AddValue("offset", resultOffset) 356 .AddValue("limit", maxResults) 357 .EndObject() 358 .EndArray() 359 .End(); 360 361 return _SendJsonRequest("userrating", jsonString, 0, message); 362 } 363 364 365 status_t 366 WebAppInterface::RetrieveUserRating(const BString& packageName, 367 const BPackageVersion& version, const BString& architecture, 368 const BString &repositoryCode, const BString& username, 369 BMessage& message) 370 { 371 // BHttpRequest later takes ownership of this. 372 BMallocIO* requestEnvelopeData = new BMallocIO(); 373 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 374 375 requestEnvelopeWriter.WriteObjectStart(); 376 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 377 "getUserRatingByUserAndPkgVersion"); 378 requestEnvelopeWriter.WriteObjectName("params"); 379 requestEnvelopeWriter.WriteArrayStart(); 380 381 requestEnvelopeWriter.WriteObjectStart(); 382 383 requestEnvelopeWriter.WriteObjectName("userNickname"); 384 requestEnvelopeWriter.WriteString(username.String()); 385 requestEnvelopeWriter.WriteObjectName("pkgName"); 386 requestEnvelopeWriter.WriteString(packageName.String()); 387 requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode"); 388 requestEnvelopeWriter.WriteString(architecture.String()); 389 requestEnvelopeWriter.WriteObjectName("repositoryCode"); 390 requestEnvelopeWriter.WriteString(repositoryCode.String()); 391 392 if (version.Major().Length() > 0) { 393 requestEnvelopeWriter.WriteObjectName("pkgVersionMajor"); 394 requestEnvelopeWriter.WriteString(version.Major().String()); 395 } 396 397 if (version.Minor().Length() > 0) { 398 requestEnvelopeWriter.WriteObjectName("pkgVersionMinor"); 399 requestEnvelopeWriter.WriteString(version.Minor().String()); 400 } 401 402 if (version.Micro().Length() > 0) { 403 requestEnvelopeWriter.WriteObjectName("pkgVersionMicro"); 404 requestEnvelopeWriter.WriteString(version.Micro().String()); 405 } 406 407 if (version.PreRelease().Length() > 0) { 408 requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease"); 409 requestEnvelopeWriter.WriteString(version.PreRelease().String()); 410 } 411 412 if (version.Revision() != 0) { 413 requestEnvelopeWriter.WriteObjectName("pkgVersionRevision"); 414 requestEnvelopeWriter.WriteInteger(version.Revision()); 415 } 416 417 requestEnvelopeWriter.WriteObjectEnd(); 418 requestEnvelopeWriter.WriteArrayEnd(); 419 requestEnvelopeWriter.WriteObjectEnd(); 420 421 return _SendJsonRequest("userrating", requestEnvelopeData, 422 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 423 message); 424 } 425 426 427 status_t 428 WebAppInterface::CreateUserRating(const BString& packageName, 429 const BPackageVersion& version, 430 const BString& architecture, const BString& repositoryCode, 431 const BString& languageCode, const BString& comment, 432 const BString& stability, int rating, BMessage& message) 433 { 434 // BHttpRequest later takes ownership of this. 435 BMallocIO* requestEnvelopeData = new BMallocIO(); 436 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 437 438 requestEnvelopeWriter.WriteObjectStart(); 439 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 440 "createUserRating"); 441 requestEnvelopeWriter.WriteObjectName("params"); 442 requestEnvelopeWriter.WriteArrayStart(); 443 444 requestEnvelopeWriter.WriteObjectStart(); 445 requestEnvelopeWriter.WriteObjectName("pkgName"); 446 requestEnvelopeWriter.WriteString(packageName.String()); 447 requestEnvelopeWriter.WriteObjectName("pkgVersionArchitectureCode"); 448 requestEnvelopeWriter.WriteString(architecture.String()); 449 requestEnvelopeWriter.WriteObjectName("repositoryCode"); 450 requestEnvelopeWriter.WriteString(repositoryCode.String()); 451 requestEnvelopeWriter.WriteObjectName("naturalLanguageCode"); 452 requestEnvelopeWriter.WriteString(languageCode.String()); 453 requestEnvelopeWriter.WriteObjectName("pkgVersionType"); 454 requestEnvelopeWriter.WriteString("SPECIFIC"); 455 requestEnvelopeWriter.WriteObjectName("userNickname"); 456 requestEnvelopeWriter.WriteString(fUsername.String()); 457 458 if (!version.Major().IsEmpty()) { 459 requestEnvelopeWriter.WriteObjectName("pkgVersionMajor"); 460 requestEnvelopeWriter.WriteString(version.Major()); 461 } 462 463 if (!version.Minor().IsEmpty()) { 464 requestEnvelopeWriter.WriteObjectName("pkgVersionMinor"); 465 requestEnvelopeWriter.WriteString(version.Minor()); 466 } 467 468 if (!version.Micro().IsEmpty()) { 469 requestEnvelopeWriter.WriteObjectName("pkgVersionMicro"); 470 requestEnvelopeWriter.WriteString(version.Micro()); 471 } 472 473 if (!version.PreRelease().IsEmpty()) { 474 requestEnvelopeWriter.WriteObjectName("pkgVersionPreRelease"); 475 requestEnvelopeWriter.WriteString(version.PreRelease()); 476 } 477 478 if (version.Revision() != 0) { 479 requestEnvelopeWriter.WriteObjectName("pkgVersionRevision"); 480 requestEnvelopeWriter.WriteInteger(version.Revision()); 481 } 482 483 if (rating > 0.0f) { 484 requestEnvelopeWriter.WriteObjectName("rating"); 485 requestEnvelopeWriter.WriteInteger(rating); 486 } 487 488 if (stability.Length() > 0) { 489 requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode"); 490 requestEnvelopeWriter.WriteString(stability); 491 } 492 493 if (comment.Length() > 0) { 494 requestEnvelopeWriter.WriteObjectName("comment"); 495 requestEnvelopeWriter.WriteString(comment.String()); 496 } 497 498 requestEnvelopeWriter.WriteObjectEnd(); 499 requestEnvelopeWriter.WriteArrayEnd(); 500 requestEnvelopeWriter.WriteObjectEnd(); 501 502 return _SendJsonRequest("userrating", requestEnvelopeData, 503 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 504 message); 505 } 506 507 508 status_t 509 WebAppInterface::UpdateUserRating(const BString& ratingID, 510 const BString& languageCode, const BString& comment, 511 const BString& stability, int rating, bool active, BMessage& message) 512 { 513 // BHttpRequest later takes ownership of this. 514 BMallocIO* requestEnvelopeData = new BMallocIO(); 515 BJsonTextWriter requestEnvelopeWriter(requestEnvelopeData); 516 517 requestEnvelopeWriter.WriteObjectStart(); 518 _WriteStandardJsonRpcEnvelopeValues(requestEnvelopeWriter, 519 "updateUserRating"); 520 521 requestEnvelopeWriter.WriteObjectName("params"); 522 requestEnvelopeWriter.WriteArrayStart(); 523 524 requestEnvelopeWriter.WriteObjectStart(); 525 526 requestEnvelopeWriter.WriteObjectName("code"); 527 requestEnvelopeWriter.WriteString(ratingID.String()); 528 requestEnvelopeWriter.WriteObjectName("naturalLanguageCode"); 529 requestEnvelopeWriter.WriteString(languageCode.String()); 530 requestEnvelopeWriter.WriteObjectName("active"); 531 requestEnvelopeWriter.WriteBoolean(active); 532 533 requestEnvelopeWriter.WriteObjectName("filter"); 534 requestEnvelopeWriter.WriteArrayStart(); 535 requestEnvelopeWriter.WriteString("ACTIVE"); 536 requestEnvelopeWriter.WriteString("NATURALLANGUAGE"); 537 requestEnvelopeWriter.WriteString("USERRATINGSTABILITY"); 538 requestEnvelopeWriter.WriteString("COMMENT"); 539 requestEnvelopeWriter.WriteString("RATING"); 540 requestEnvelopeWriter.WriteArrayEnd(); 541 542 if (rating >= 0) { 543 requestEnvelopeWriter.WriteObjectName("rating"); 544 requestEnvelopeWriter.WriteInteger(rating); 545 } 546 547 if (stability.Length() > 0) { 548 requestEnvelopeWriter.WriteObjectName("userRatingStabilityCode"); 549 requestEnvelopeWriter.WriteString(stability); 550 } 551 552 if (comment.Length() > 0) { 553 requestEnvelopeWriter.WriteObjectName("comment"); 554 requestEnvelopeWriter.WriteString(comment); 555 } 556 557 requestEnvelopeWriter.WriteObjectEnd(); 558 requestEnvelopeWriter.WriteArrayEnd(); 559 requestEnvelopeWriter.WriteObjectEnd(); 560 561 return _SendJsonRequest("userrating", requestEnvelopeData, 562 _LengthAndSeekToZero(requestEnvelopeData), NEEDS_AUTHORIZATION, 563 message); 564 } 565 566 567 status_t 568 WebAppInterface::RetrieveScreenshot(const BString& code, 569 int32 width, int32 height, BDataIO* stream) 570 { 571 BUrl url = ServerSettings::CreateFullUrl( 572 BString("/__pkgscreenshot/") << code << ".png" << "?tw=" 573 << width << "&th=" << height); 574 575 bool isSecure = url.Protocol() == "https"; 576 577 ProtocolListener listener(Logger::IsTraceEnabled()); 578 listener.SetDownloadIO(stream); 579 580 BHttpHeaders headers; 581 ServerSettings::AugmentHeaders(headers); 582 583 BHttpRequest request(url, isSecure, "HTTP", &listener); 584 request.SetMethod(B_HTTP_GET); 585 request.SetHeaders(headers); 586 587 thread_id thread = request.Run(); 588 wait_for_thread(thread, NULL); 589 590 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 591 request.Result()); 592 593 int32 statusCode = result.StatusCode(); 594 595 if (statusCode == 200) 596 return B_OK; 597 598 fprintf(stderr, "failed to get screenshot from '%s': %" B_PRIi32 "\n", 599 url.UrlString().String(), statusCode); 600 return B_ERROR; 601 } 602 603 604 status_t 605 WebAppInterface::RequestCaptcha(BMessage& message) 606 { 607 BString jsonString = JsonBuilder() 608 .AddValue("jsonrpc", "2.0") 609 .AddValue("id", ++fRequestIndex) 610 .AddValue("method", "generateCaptcha") 611 .AddArray("params") 612 .AddObject() 613 .EndObject() 614 .EndArray() 615 .End(); 616 617 return _SendJsonRequest("captcha", jsonString, 0, message); 618 } 619 620 621 status_t 622 WebAppInterface::CreateUser(const BString& nickName, 623 const BString& passwordClear, const BString& email, 624 const BString& captchaToken, const BString& captchaResponse, 625 const BString& languageCode, BMessage& message) 626 { 627 JsonBuilder builder; 628 builder 629 .AddValue("jsonrpc", "2.0") 630 .AddValue("id", ++fRequestIndex) 631 .AddValue("method", "createUser") 632 .AddArray("params") 633 .AddObject() 634 .AddValue("nickname", nickName) 635 .AddValue("passwordClear", passwordClear); 636 637 if (!email.IsEmpty()) 638 builder.AddValue("email", email); 639 640 builder.AddValue("captchaToken", captchaToken) 641 .AddValue("captchaResponse", captchaResponse) 642 .AddValue("naturalLanguageCode", languageCode) 643 .EndObject() 644 .EndArray() 645 ; 646 647 BString jsonString = builder.End(); 648 649 return _SendJsonRequest("user", jsonString, 0, message); 650 } 651 652 653 status_t 654 WebAppInterface::AuthenticateUser(const BString& nickName, 655 const BString& passwordClear, BMessage& message) 656 { 657 BString jsonString = JsonBuilder() 658 .AddValue("jsonrpc", "2.0") 659 .AddValue("id", ++fRequestIndex) 660 .AddValue("method", "authenticateUser") 661 .AddArray("params") 662 .AddObject() 663 .AddValue("nickname", nickName) 664 .AddValue("passwordClear", passwordClear) 665 .EndObject() 666 .EndArray() 667 .End(); 668 669 return _SendJsonRequest("user", jsonString, 0, message); 670 } 671 672 673 /*! JSON-RPC invocations return a response. The response may be either 674 a result or it may be an error depending on the response structure. 675 If it is an error then there may be additional detail that is the 676 error code and message. This method will extract the error code 677 from the response. This method will return 0 if the payload does 678 not look like an error. 679 */ 680 681 int32 682 WebAppInterface::ErrorCodeFromResponse(BMessage& response) 683 { 684 BMessage error; 685 double code; 686 687 if (response.FindMessage("error", &error) == B_OK 688 && error.FindDouble("code", &code) == B_OK) { 689 return (int32) code; 690 } 691 692 return 0; 693 } 694 695 696 // #pragma mark - private 697 698 699 void 700 WebAppInterface::_WriteStandardJsonRpcEnvelopeValues(BJsonWriter& writer, 701 const char* methodName) 702 { 703 writer.WriteObjectName("jsonrpc"); 704 writer.WriteString("2.0"); 705 writer.WriteObjectName("id"); 706 writer.WriteInteger(++fRequestIndex); 707 writer.WriteObjectName("method"); 708 writer.WriteString(methodName); 709 } 710 711 712 status_t 713 WebAppInterface::_SendJsonRequest(const char* domain, BPositionIO* requestData, 714 size_t requestDataSize, uint32 flags, BMessage& reply) const 715 { 716 if (requestDataSize == 0) { 717 if (Logger::IsInfoEnabled()) 718 printf("jrpc; empty request payload\n"); 719 return B_ERROR; 720 } 721 722 if (!ServerHelper::IsNetworkAvailable()) { 723 if (Logger::IsDebugEnabled()) { 724 printf("jrpc; dropping request to ...[%s] as network is not " 725 "available\n", domain); 726 } 727 delete requestData; 728 return HD_NETWORK_INACCESSIBLE; 729 } 730 731 if (ServerSettings::IsClientTooOld()) { 732 if (Logger::IsDebugEnabled()) { 733 printf("jrpc; dropping request to ...[%s] as client is too " 734 "old\n", domain); 735 } 736 delete requestData; 737 return HD_CLIENT_TOO_OLD; 738 } 739 740 BUrl url = ServerSettings::CreateFullUrl(BString("/__api/v1/") << domain); 741 bool isSecure = url.Protocol() == "https"; 742 743 if (Logger::IsDebugEnabled()) { 744 printf("jrpc; will make request to [%s]\n", 745 url.UrlString().String()); 746 } 747 748 // If the request payload is logged then it must be copied to local memory 749 // from the stream. This then requires that the request data is then 750 // delivered from memory. 751 752 if (Logger::IsTraceEnabled()) { 753 printf("jrpc request; "); 754 _LogPayload(requestData, requestDataSize); 755 printf("\n"); 756 } 757 758 ProtocolListener listener(Logger::IsTraceEnabled()); 759 BUrlContext context; 760 761 BHttpHeaders headers; 762 headers.AddHeader("Content-Type", "application/json"); 763 ServerSettings::AugmentHeaders(headers); 764 765 BHttpRequest request(url, isSecure, "HTTP", &listener, &context); 766 request.SetMethod(B_HTTP_POST); 767 request.SetHeaders(headers); 768 769 // Authentication via Basic Authentication 770 // The other way would be to obtain a token and then use the Token Bearer 771 // header. 772 if ((flags & NEEDS_AUTHORIZATION) != 0 773 && !fUsername.IsEmpty() && !fPassword.IsEmpty()) { 774 BHttpAuthentication authentication(fUsername, fPassword); 775 authentication.SetMethod(B_HTTP_AUTHENTICATION_BASIC); 776 context.AddAuthentication(url, authentication); 777 } 778 779 780 request.AdoptInputData(requestData, requestDataSize); 781 782 BMallocIO replyData; 783 listener.SetDownloadIO(&replyData); 784 785 thread_id thread = request.Run(); 786 wait_for_thread(thread, NULL); 787 788 const BHttpResult& result = dynamic_cast<const BHttpResult&>( 789 request.Result()); 790 791 int32 statusCode = result.StatusCode(); 792 793 if (Logger::IsDebugEnabled()) { 794 printf("jrpc; did receive http-status [%" B_PRId32 "] " 795 "from [%s]\n", statusCode, url.UrlString().String()); 796 } 797 798 switch (statusCode) { 799 case B_HTTP_STATUS_OK: 800 break; 801 802 case B_HTTP_STATUS_PRECONDITION_FAILED: 803 ServerHelper::NotifyClientTooOld(result.Headers()); 804 return HD_CLIENT_TOO_OLD; 805 806 default: 807 printf("jrpc request to endpoint [.../%s] failed with http " 808 "status [%" B_PRId32 "]\n", domain, statusCode); 809 return B_ERROR; 810 } 811 812 replyData.Seek(0, SEEK_SET); 813 814 if (Logger::IsTraceEnabled()) { 815 printf("jrpc response; "); 816 _LogPayload(&replyData, replyData.BufferLength()); 817 printf("\n"); 818 } 819 820 BJsonMessageWriter jsonMessageWriter(reply); 821 BJson::Parse(&replyData, &jsonMessageWriter); 822 status_t status = jsonMessageWriter.ErrorStatus(); 823 824 if (Logger::IsTraceEnabled() && status == B_BAD_DATA) { 825 BString resultString(static_cast<const char *>(replyData.Buffer()), 826 replyData.BufferLength()); 827 printf("Parser choked on JSON:\n%s\n", resultString.String()); 828 } 829 return status; 830 } 831 832 833 status_t 834 WebAppInterface::_SendJsonRequest(const char* domain, const BString& jsonString, 835 uint32 flags, BMessage& reply) const 836 { 837 // gets 'adopted' by the subsequent http request. 838 BMemoryIO* data = new BMemoryIO(jsonString.String(), 839 jsonString.Length() - 1); 840 841 return _SendJsonRequest(domain, data, jsonString.Length() - 1, flags, 842 reply); 843 } 844 845 846 void 847 WebAppInterface::_LogPayload(BPositionIO* requestData, size_t size) 848 { 849 off_t requestDataOffset = requestData->Position(); 850 char buffer[LOG_PAYLOAD_LIMIT]; 851 852 if (size > LOG_PAYLOAD_LIMIT) 853 size = LOG_PAYLOAD_LIMIT; 854 855 if (B_OK != requestData->ReadExactly(buffer, size)) { 856 printf("jrpc; error logging payload\n"); 857 } else { 858 for (uint32 i = 0; i < size; i++) { 859 bool esc = buffer[i] > 126 || 860 (buffer[i] < 0x20 && buffer[i] != 0x0a); 861 862 if (esc) 863 printf("\\u%02x", buffer[i]); 864 else 865 putchar(buffer[i]); 866 } 867 868 if (size == LOG_PAYLOAD_LIMIT) 869 printf("...(continues)"); 870 } 871 872 requestData->Seek(requestDataOffset, SEEK_SET); 873 } 874 875 876 /*! This will get the position of the data to get the length an then sets the 877 offset to zero so that it can be re-read for reading the payload in to log 878 or send. 879 */ 880 881 off_t 882 WebAppInterface::_LengthAndSeekToZero(BPositionIO* data) 883 { 884 off_t dataSize = data->Position(); 885 data->Seek(0, SEEK_SET); 886 return dataSize; 887 } 888