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