tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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"