backend.cpp (24191B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "backend.h" 6 7 #include "mozilla/Logging.h" 8 #include "mozilla/Span.h" 9 #include "nsCOMPtr.h" 10 #include "nsComponentManagerUtils.h" 11 #include "nsContentUtils.h" 12 #include "nsIChannel.h" 13 #include "nsIHttpChannel.h" 14 #include "nsIHttpChannelInternal.h" 15 #include "nsIHttpHeaderVisitor.h" 16 #include "nsIInputStream.h" 17 #include "nsIStreamListener.h" 18 #include "nsITimer.h" 19 #include "nsIUploadChannel2.h" 20 #include "nsIURI.h" 21 #include "nsNetUtil.h" 22 #include "nsPrintfCString.h" 23 #include "nsStringStream.h" 24 #include "nsThreadUtils.h" 25 #include "nsTArray.h" 26 27 #include <cinttypes> 28 #include <utility> 29 30 using namespace mozilla; 31 32 // Logger for viaduct-necko backend 33 static LazyLogModule gViaductLogger("viaduct"); 34 35 /** 36 * Manages viaduct Request/Result pointers 37 * 38 * This class ensures that we properly manage the `ViaductRequest` and 39 * `ViaductResult` pointers, avoiding use-after-free bugs. It ensures that 40 * either `viaduct_necko_result_complete` or 41 * `viaduct_necko_result_complete_error` will be called exactly once and the 42 * pointers won't be used after that. 43 * 44 * This class is designed to be created outside of NS_DispatchToMainThread and 45 * moved into the closure. This way, even if the closure never runs, the 46 * destructor will still be called and we'll complete with an error. 47 */ 48 class ViaductRequestGuard { 49 private: 50 const ViaductRequest* mRequest; 51 ViaductResult* mResult; 52 53 public: 54 // Constructor 55 ViaductRequestGuard(const ViaductRequest* aRequest, ViaductResult* aResult) 56 : mRequest(aRequest), mResult(aResult) { 57 MOZ_LOG(gViaductLogger, LogLevel::Debug, 58 ("ViaductRequestGuard: Created with request=%p, result=%p", 59 mRequest, mResult)); 60 } 61 62 // Move Constructor 63 // Transfers ownership of the pointers from other to this. 64 ViaductRequestGuard(ViaductRequestGuard&& other) noexcept 65 : mRequest(std::exchange(other.mRequest, nullptr)), 66 mResult(std::exchange(other.mResult, nullptr)) { 67 MOZ_LOG(gViaductLogger, LogLevel::Debug, 68 ("ViaductRequestGuard: Move constructed, request=%p, result=%p", 69 mRequest, mResult)); 70 } 71 72 // Move assignment operator 73 ViaductRequestGuard& operator=(ViaductRequestGuard&& other) noexcept { 74 if (this != &other) { 75 // If we already own pointers, complete with error before replacing 76 if (mResult) { 77 MOZ_LOG(gViaductLogger, LogLevel::Warning, 78 ("ViaductRequestGuard: Move assignment replacing existing " 79 "pointers, completing with error")); 80 viaduct_necko_result_complete_error( 81 mResult, static_cast<uint32_t>(NS_ERROR_ABORT), 82 "Request replaced by move assignment"); 83 } 84 mRequest = std::exchange(other.mRequest, nullptr); 85 mResult = std::exchange(other.mResult, nullptr); 86 } 87 return *this; 88 } 89 90 // Disable copy constructor and assignment 91 // We prevent copying since we only want to complete the result once. 92 ViaductRequestGuard(const ViaductRequestGuard& other) = delete; 93 ViaductRequestGuard& operator=(const ViaductRequestGuard& other) = delete; 94 95 ~ViaductRequestGuard() { 96 // If mResult is non-null, the request was destroyed before completing. 97 // This can happen if the closure never runs (e.g., shutdown). 98 if (mResult) { 99 MOZ_LOG(gViaductLogger, LogLevel::Warning, 100 ("ViaductRequestGuard: Destructor called with non-null result, " 101 "completing with error")); 102 viaduct_necko_result_complete_error( 103 mResult, static_cast<uint32_t>(NS_ERROR_ABORT), 104 "Request destroyed without completion"); 105 } 106 } 107 108 // Get the request pointer (for reading request data) 109 // Returns nullptr if already consumed. 110 const ViaductRequest* Request() const { 111 MOZ_ASSERT(mRequest, 112 "ViaductRequestGuard::Request called after completion"); 113 return mRequest; 114 } 115 116 // Get the result pointer (for building up the response) 117 // Returns nullptr if already consumed. 118 ViaductResult* Result() const { 119 MOZ_ASSERT(mResult, "ViaductRequestGuard::Result called after completion"); 120 return mResult; 121 } 122 123 // Check if the guard still owns valid pointers 124 bool IsValid() const { return mResult != nullptr; } 125 126 // Complete the result successfully and release ownership. 127 // After this call, the guard no longer owns the pointers. 128 void Complete() { 129 MOZ_ASSERT(mResult, "ViaductRequestGuard::Complete called twice"); 130 MOZ_LOG(gViaductLogger, LogLevel::Debug, 131 ("ViaductRequestGuard: Completing successfully")); 132 viaduct_necko_result_complete(mResult); 133 mResult = nullptr; 134 mRequest = nullptr; 135 } 136 137 // Complete the result with an error and release ownership. 138 // After this call, the guard no longer owns the pointers. 139 void CompleteWithError(nsresult aError, const char* aMessage) { 140 MOZ_ASSERT(mResult, "ViaductRequestGuard::CompleteWithError called twice"); 141 MOZ_LOG(gViaductLogger, LogLevel::Error, 142 ("ViaductRequestGuard: Completing with error: %s (0x%08x)", 143 aMessage, static_cast<uint32_t>(aError))); 144 viaduct_necko_result_complete_error(mResult, static_cast<uint32_t>(aError), 145 aMessage); 146 mResult = nullptr; 147 mRequest = nullptr; 148 } 149 }; 150 151 // Listener that collects the complete HTTP response (headers and body) 152 class ViaductResponseListener final : public nsIHttpHeaderVisitor, 153 public nsIStreamListener, 154 public nsITimerCallback, 155 public nsINamed { 156 public: 157 NS_DECL_THREADSAFE_ISUPPORTS 158 NS_DECL_NSIHTTPHEADERVISITOR 159 NS_DECL_NSIREQUESTOBSERVER 160 NS_DECL_NSISTREAMLISTENER 161 NS_DECL_NSITIMERCALLBACK 162 NS_DECL_NSINAMED 163 164 // Use Create() instead of calling the constructor directly. 165 // Timer creation must happen after a RefPtr holds a reference. 166 // Returns nullptr if timer creation fails (when aTimeoutSecs > 0). 167 static already_AddRefed<ViaductResponseListener> Create( 168 ViaductRequestGuard&& aGuard, uint32_t aTimeoutSecs, 169 nsresult* aOutTimerRv = nullptr) { 170 RefPtr<ViaductResponseListener> listener = 171 new ViaductResponseListener(std::move(aGuard)); 172 nsresult rv = listener->StartTimeoutTimer(aTimeoutSecs); 173 if (aOutTimerRv) { 174 *aOutTimerRv = rv; 175 } 176 if (NS_FAILED(rv)) { 177 return nullptr; 178 } 179 return listener.forget(); 180 } 181 182 void SetChannel(nsIChannel* aChannel) { mChannel = aChannel; } 183 184 private: 185 explicit ViaductResponseListener(ViaductRequestGuard&& aGuard) 186 : mGuard(std::move(aGuard)), mChannel(nullptr) { 187 MOZ_LOG(gViaductLogger, LogLevel::Info, 188 ("TRACE: ViaductResponseListener constructor called, guard valid: " 189 "%s", 190 mGuard.IsValid() ? "true" : "false")); 191 } 192 193 nsresult StartTimeoutTimer(uint32_t aTimeoutSecs) { 194 if (aTimeoutSecs == 0) { 195 return NS_OK; 196 } 197 MOZ_LOG(gViaductLogger, LogLevel::Debug, 198 ("Setting timeout timer for %u seconds", aTimeoutSecs)); 199 nsresult rv = 200 NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer), this, 201 aTimeoutSecs * 1000, nsITimer::TYPE_ONE_SHOT); 202 if (NS_FAILED(rv)) { 203 MOZ_LOG(gViaductLogger, LogLevel::Error, 204 ("Failed to create timeout timer: 0x%08x", 205 static_cast<uint32_t>(rv))); 206 } 207 return rv; 208 } 209 210 ~ViaductResponseListener() { 211 MOZ_LOG(gViaductLogger, LogLevel::Info, 212 ("TRACE: ViaductResponseListener destructor called")); 213 214 ClearTimer(); 215 216 // The guard's destructor will handle completion if needed 217 } 218 219 void ClearTimer() { 220 if (mTimeoutTimer) { 221 mTimeoutTimer->Cancel(); 222 mTimeoutTimer = nullptr; 223 } 224 } 225 226 // Error handling: logs error and completes the result with error via the 227 // guard. 228 void HandleError(nsresult aError, const char* aMessage); 229 230 // Wrapper methods that use the guard to safely access the result 231 void SetStatusCode(uint16_t aStatusCode); 232 void SetUrl(const char* aUrl, size_t aLength); 233 void AddHeader(const char* aKey, size_t aKeyLength, const char* aValue, 234 size_t aValueLength); 235 void ExtendBody(const uint8_t* aData, size_t aLength); 236 void Complete(); 237 238 ViaductRequestGuard mGuard; 239 nsCOMPtr<nsITimer> mTimeoutTimer; 240 nsCOMPtr<nsIChannel> mChannel; 241 }; 242 243 NS_IMPL_ISUPPORTS(ViaductResponseListener, nsIHttpHeaderVisitor, 244 nsIStreamListener, nsIRequestObserver, nsITimerCallback, 245 nsINamed) 246 247 void ViaductResponseListener::HandleError(nsresult aError, 248 const char* aMessage) { 249 MOZ_LOG(gViaductLogger, LogLevel::Error, 250 ("TRACE: HandleError called with message: %s (0x%08x)", aMessage, 251 static_cast<uint32_t>(aError))); 252 253 if (mGuard.IsValid()) { 254 MOZ_LOG(gViaductLogger, LogLevel::Info, 255 ("TRACE: Calling CompleteWithError via guard")); 256 mGuard.CompleteWithError(aError, aMessage); 257 } else { 258 MOZ_LOG(gViaductLogger, LogLevel::Error, 259 ("TRACE: HandleError called but guard is invalid")); 260 } 261 } 262 263 void ViaductResponseListener::SetStatusCode(uint16_t aStatusCode) { 264 MOZ_LOG(gViaductLogger, LogLevel::Info, 265 ("TRACE: SetStatusCode called with code: %u", aStatusCode)); 266 if (!mGuard.IsValid()) { 267 MOZ_LOG(gViaductLogger, LogLevel::Error, 268 ("SetStatusCode called but guard is invalid")); 269 return; 270 } 271 viaduct_necko_result_set_status_code(mGuard.Result(), aStatusCode); 272 MOZ_LOG(gViaductLogger, LogLevel::Debug, 273 ("Set status code: %u", aStatusCode)); 274 } 275 276 void ViaductResponseListener::SetUrl(const char* aUrl, size_t aLength) { 277 MOZ_LOG(gViaductLogger, LogLevel::Info, 278 ("TRACE: SetUrl called with URL (length %zu)", aLength)); 279 if (!mGuard.IsValid()) { 280 MOZ_LOG(gViaductLogger, LogLevel::Error, 281 ("SetUrl called but guard is invalid")); 282 return; 283 } 284 viaduct_necko_result_set_url(mGuard.Result(), aUrl, aLength); 285 MOZ_LOG(gViaductLogger, LogLevel::Debug, ("Set URL")); 286 } 287 288 void ViaductResponseListener::AddHeader(const char* aKey, size_t aKeyLength, 289 const char* aValue, 290 size_t aValueLength) { 291 MOZ_LOG(gViaductLogger, LogLevel::Info, 292 ("TRACE: AddHeader called - key length: %zu, value length: %zu", 293 aKeyLength, aValueLength)); 294 if (!mGuard.IsValid()) { 295 MOZ_LOG(gViaductLogger, LogLevel::Error, 296 ("AddHeader called but guard is invalid")); 297 return; 298 } 299 viaduct_necko_result_add_header(mGuard.Result(), aKey, aKeyLength, aValue, 300 aValueLength); 301 MOZ_LOG(gViaductLogger, LogLevel::Debug, ("Added header")); 302 } 303 304 void ViaductResponseListener::ExtendBody(const uint8_t* aData, size_t aLength) { 305 MOZ_LOG(gViaductLogger, LogLevel::Info, 306 ("TRACE: ExtendBody called with %zu bytes", aLength)); 307 if (!mGuard.IsValid()) { 308 MOZ_LOG(gViaductLogger, LogLevel::Error, 309 ("ExtendBody called but guard is invalid")); 310 return; 311 } 312 viaduct_necko_result_extend_body(mGuard.Result(), aData, aLength); 313 MOZ_LOG(gViaductLogger, LogLevel::Debug, 314 ("Extended body with %zu bytes", aLength)); 315 } 316 317 void ViaductResponseListener::Complete() { 318 MOZ_LOG(gViaductLogger, LogLevel::Info, 319 ("TRACE: Complete called - marking request as successful")); 320 if (!mGuard.IsValid()) { 321 MOZ_LOG(gViaductLogger, LogLevel::Error, 322 ("Complete called but guard is invalid")); 323 return; 324 } 325 MOZ_LOG(gViaductLogger, LogLevel::Info, 326 ("TRACE: Calling Complete via guard")); 327 mGuard.Complete(); 328 } 329 330 NS_IMETHODIMP 331 ViaductResponseListener::VisitHeader(const nsACString& aHeader, 332 const nsACString& aValue) { 333 MOZ_LOG(gViaductLogger, LogLevel::Info, 334 ("TRACE: VisitHeader called for header: %s", 335 PromiseFlatCString(aHeader).get())); 336 AddHeader(aHeader.BeginReading(), aHeader.Length(), aValue.BeginReading(), 337 aValue.Length()); 338 return NS_OK; 339 } 340 341 NS_IMETHODIMP 342 ViaductResponseListener::OnStartRequest(nsIRequest* aRequest) { 343 MOZ_LOG(gViaductLogger, LogLevel::Info, 344 ("TRACE: ========== OnStartRequest called ==========")); 345 346 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); 347 if (!httpChannel) { 348 HandleError(NS_ERROR_FAILURE, "Request is not an HTTP channel"); 349 return NS_ERROR_FAILURE; 350 } 351 352 // Get status code from HTTP channel 353 uint32_t responseStatus; 354 nsresult rv = httpChannel->GetResponseStatus(&responseStatus); 355 if (NS_FAILED(rv)) { 356 HandleError(rv, "Failed to get response status"); 357 return rv; 358 } 359 SetStatusCode(static_cast<uint16_t>(responseStatus)); 360 361 // Get final URL 362 nsCOMPtr<nsIURI> uri; 363 rv = httpChannel->GetURI(getter_AddRefs(uri)); 364 if (NS_FAILED(rv)) { 365 HandleError(rv, "Failed to get URI"); 366 return rv; 367 } 368 369 if (!uri) { 370 HandleError(NS_ERROR_FAILURE, "HTTP channel has null URI"); 371 return NS_ERROR_FAILURE; 372 } 373 374 nsAutoCString spec; 375 rv = uri->GetSpec(spec); 376 if (NS_FAILED(rv)) { 377 HandleError(rv, "Failed to get URI spec"); 378 return rv; 379 } 380 SetUrl(spec.get(), spec.Length()); 381 382 // Collect response headers - using 'this' since we implement 383 // nsIHttpHeaderVisitor 384 MOZ_LOG(gViaductLogger, LogLevel::Info, 385 ("TRACE: About to visit response headers")); 386 rv = httpChannel->VisitResponseHeaders(this); 387 if (NS_FAILED(rv)) { 388 HandleError(rv, "Failed to visit response headers"); 389 return rv; 390 } 391 392 return NS_OK; 393 } 394 395 NS_IMETHODIMP 396 ViaductResponseListener::OnDataAvailable(nsIRequest* aRequest, 397 nsIInputStream* aInputStream, 398 uint64_t aOffset, uint32_t aCount) { 399 MOZ_LOG(gViaductLogger, LogLevel::Debug, 400 ("OnDataAvailable called with %u bytes at offset %" PRIu64, aCount, 401 aOffset)); 402 403 // Read the data from the input stream 404 nsTArray<uint8_t> buffer; 405 buffer.SetLength(aCount); 406 407 uint32_t bytesRead; 408 nsresult rv = aInputStream->Read(reinterpret_cast<char*>(buffer.Elements()), 409 aCount, &bytesRead); 410 if (NS_FAILED(rv)) { 411 HandleError(rv, "Failed to read from input stream"); 412 return rv; 413 } 414 415 if (bytesRead > 0) { 416 ExtendBody(buffer.Elements(), bytesRead); 417 } else { 418 MOZ_LOG(gViaductLogger, LogLevel::Warning, 419 ("Read 0 bytes from input stream")); 420 } 421 422 return NS_OK; 423 } 424 425 NS_IMETHODIMP 426 ViaductResponseListener::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { 427 MOZ_LOG(gViaductLogger, LogLevel::Debug, 428 ("OnStopRequest called with status: 0x%08x", 429 static_cast<uint32_t>(aStatus))); 430 431 // Cancel timer since request is complete 432 ClearTimer(); 433 434 if (NS_SUCCEEDED(aStatus)) { 435 Complete(); 436 } else { 437 HandleError(aStatus, "Request failed"); 438 } 439 440 return NS_OK; 441 } 442 443 /////////////////////////////////////////////////////////////////////////////// 444 // nsITimerCallback implementation 445 446 NS_IMETHODIMP 447 ViaductResponseListener::Notify(nsITimer* aTimer) { 448 MOZ_LOG(gViaductLogger, LogLevel::Warning, 449 ("TRACE: Request timeout fired - cancelling request")); 450 451 ClearTimer(); 452 453 // Cancel the channel, which will trigger OnStopRequest with an error 454 if (mChannel) { 455 mChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL); 456 mChannel = nullptr; 457 } 458 459 return NS_OK; 460 } 461 462 /////////////////////////////////////////////////////////////////////////////// 463 // nsINamed implementation 464 465 NS_IMETHODIMP 466 ViaductResponseListener::GetName(nsACString& aName) { 467 aName.AssignLiteral("ViaductResponseListener"); 468 return NS_OK; 469 } 470 471 // Convert ViaductMethod to HTTP method string 472 static const char* GetMethodString(ViaductMethod method) { 473 switch (method) { 474 case VIADUCT_METHOD_GET: 475 return "GET"; 476 case VIADUCT_METHOD_HEAD: 477 return "HEAD"; 478 case VIADUCT_METHOD_POST: 479 return "POST"; 480 case VIADUCT_METHOD_PUT: 481 return "PUT"; 482 case VIADUCT_METHOD_DELETE: 483 return "DELETE"; 484 case VIADUCT_METHOD_CONNECT: 485 return "CONNECT"; 486 case VIADUCT_METHOD_OPTIONS: 487 return "OPTIONS"; 488 case VIADUCT_METHOD_TRACE: 489 return "TRACE"; 490 case VIADUCT_METHOD_PATCH: 491 return "PATCH"; 492 default: 493 MOZ_LOG(gViaductLogger, LogLevel::Warning, 494 ("Unknown ViaductMethod: %d, defaulting to GET", method)); 495 return "GET"; 496 } 497 } 498 499 extern "C" { 500 501 void viaduct_necko_backend_init() { 502 MOZ_LOG(gViaductLogger, LogLevel::Info, 503 ("Viaduct Necko backend initialized")); 504 } 505 506 void viaduct_necko_backend_send_request(const ViaductRequest* request, 507 ViaductResult* result) { 508 MOZ_LOG(gViaductLogger, LogLevel::Debug, ("send_request called")); 509 510 MOZ_ASSERT(request, "Request pointer should not be null"); 511 MOZ_ASSERT(result, "Result pointer should not be null"); 512 513 // Create a guard to manage the request/result pointer lifetime. 514 // This ensures that either viaduct_necko_result_complete or 515 // viaduct_necko_result_complete_error is called exactly once, 516 // even if the closure never runs (e.g., during shutdown). 517 ViaductRequestGuard guard(request, result); 518 519 // This function is called from Rust on a background thread. 520 // We need to dispatch to the main thread to use Necko. 521 NS_DispatchToMainThread(NS_NewRunnableFunction( 522 "ViaductNeckoRequest", [guard = std::move(guard)]() mutable { 523 MOZ_LOG(gViaductLogger, LogLevel::Debug, 524 ("Executing request on main thread")); 525 526 MOZ_ASSERT(guard.Request() && guard.Result(), 527 "Guard should have valid pointers"); 528 529 nsresult rv; 530 531 // Parse the URL 532 nsCOMPtr<nsIURI> uri; 533 nsAutoCString urlSpec(guard.Request()->url); 534 MOZ_LOG(gViaductLogger, LogLevel::Debug, 535 ("Parsing URL: %s", urlSpec.get())); 536 537 rv = NS_NewURI(getter_AddRefs(uri), urlSpec); 538 if (NS_FAILED(rv)) { 539 guard.CompleteWithError(rv, "Failed to parse URL"); 540 return; 541 } 542 543 // Create the channel 544 nsSecurityFlags secFlags = 545 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL | 546 nsILoadInfo::SEC_COOKIES_OMIT; 547 548 nsCOMPtr<nsIChannel> channel; 549 rv = NS_NewChannel(getter_AddRefs(channel), uri, 550 nsContentUtils::GetSystemPrincipal(), secFlags, 551 nsIContentPolicy::TYPE_OTHER); 552 553 if (NS_FAILED(rv)) { 554 guard.CompleteWithError(rv, "Failed to create channel"); 555 return; 556 } 557 558 if (!channel) { 559 guard.CompleteWithError(NS_ERROR_FAILURE, 560 "NS_NewChannel returned null channel"); 561 return; 562 } 563 564 // Get the HTTP channel interface 565 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel); 566 if (!httpChannel) { 567 guard.CompleteWithError(NS_ERROR_FAILURE, 568 "Channel is not an HTTP channel"); 569 return; 570 } 571 572 // Set HTTP method 573 const char* methodStr = GetMethodString(guard.Request()->method); 574 MOZ_LOG(gViaductLogger, LogLevel::Debug, 575 ("Setting HTTP method: %s", methodStr)); 576 rv = httpChannel->SetRequestMethod(nsDependentCString(methodStr)); 577 if (NS_FAILED(rv)) { 578 guard.CompleteWithError(rv, "Failed to set request method"); 579 return; 580 } 581 582 // Set request headers 583 MOZ_LOG(gViaductLogger, LogLevel::Debug, 584 ("Setting %zu request headers", guard.Request()->header_count)); 585 for (size_t i = 0; i < guard.Request()->header_count; i++) { 586 nsAutoCString key(guard.Request()->headers[i].key); 587 nsAutoCString value(guard.Request()->headers[i].value); 588 rv = httpChannel->SetRequestHeader(key, value, false); 589 if (NS_FAILED(rv)) { 590 guard.CompleteWithError(rv, "Failed to set request header"); 591 return; 592 } 593 } 594 595 // Set redirect limit 596 if (guard.Request()->redirect_limit == 0) { 597 // Disable redirects entirely 598 MOZ_LOG(gViaductLogger, LogLevel::Debug, ("Disabling redirects")); 599 nsCOMPtr<nsIHttpChannelInternal> httpInternal = 600 do_QueryInterface(httpChannel); 601 if (!httpInternal) { 602 guard.CompleteWithError( 603 NS_ERROR_FAILURE, 604 "Failed to get nsIHttpChannelInternal interface"); 605 return; 606 } 607 rv = httpInternal->SetRedirectMode( 608 nsIHttpChannelInternal::REDIRECT_MODE_ERROR); 609 if (NS_FAILED(rv)) { 610 guard.CompleteWithError(rv, "Failed to set redirect mode"); 611 return; 612 } 613 } else { 614 // Set a specific redirect limit 615 MOZ_LOG( 616 gViaductLogger, LogLevel::Debug, 617 ("Setting redirect limit: %u", guard.Request()->redirect_limit)); 618 rv = 619 httpChannel->SetRedirectionLimit(guard.Request()->redirect_limit); 620 if (NS_FAILED(rv)) { 621 guard.CompleteWithError(rv, "Failed to set redirection limit"); 622 return; 623 } 624 } 625 626 // Set request body if present 627 if (guard.Request()->body != nullptr && guard.Request()->body_len > 0) { 628 MOZ_LOG( 629 gViaductLogger, LogLevel::Debug, 630 ("Setting request body (%zu bytes)", guard.Request()->body_len)); 631 nsCOMPtr<nsIUploadChannel2> uploadChannel = 632 do_QueryInterface(httpChannel); 633 if (!uploadChannel) { 634 guard.CompleteWithError( 635 NS_ERROR_FAILURE, "Failed to get nsIUploadChannel2 interface"); 636 return; 637 } 638 639 nsCOMPtr<nsIInputStream> bodyStream; 640 rv = NS_NewByteInputStream( 641 getter_AddRefs(bodyStream), 642 Span(reinterpret_cast<const char*>(guard.Request()->body), 643 guard.Request()->body_len), 644 NS_ASSIGNMENT_COPY); 645 if (NS_FAILED(rv)) { 646 guard.CompleteWithError(rv, "Failed to create body stream"); 647 return; 648 } 649 650 rv = uploadChannel->ExplicitSetUploadStream( 651 bodyStream, VoidCString(), guard.Request()->body_len, 652 nsDependentCString(methodStr), false); 653 if (NS_FAILED(rv)) { 654 guard.CompleteWithError(rv, "Failed to set upload stream"); 655 return; 656 } 657 } 658 659 // Get timeout before moving the guard 660 uint32_t timeout = guard.Request()->timeout; 661 662 // Create listener using factory method. This ensures the timer is 663 // created after a RefPtr holds a reference. 664 nsresult timerRv; 665 RefPtr<ViaductResponseListener> listener = 666 ViaductResponseListener::Create(std::move(guard), timeout, 667 &timerRv); 668 if (!listener) { 669 MOZ_LOG(gViaductLogger, LogLevel::Error, 670 ("Failed to create listener: timer creation failed 0x%08x", 671 static_cast<uint32_t>(timerRv))); 672 return; 673 } 674 675 // Store the channel in the listener so it can cancel it on timeout. 676 listener->SetChannel(channel); 677 678 MOZ_LOG(gViaductLogger, LogLevel::Debug, ("Opening HTTP channel")); 679 rv = httpChannel->AsyncOpen(listener); 680 681 if (NS_FAILED(rv)) { 682 MOZ_LOG(gViaductLogger, LogLevel::Error, 683 ("AsyncOpen failed: 0x%08x. Guard was moved to listener, " 684 "destructor will handle cleanup and complete with error.", 685 static_cast<uint32_t>(rv))); 686 return; 687 } 688 689 MOZ_LOG(gViaductLogger, LogLevel::Debug, 690 ("Request initiated successfully")); 691 // The request is now in progress. The listener will handle 692 // completion. 693 })); 694 } 695 696 } // extern "C"