You can attach listeners to a View so you can save files downloaded by the page and allow, block, or secure outgoing network requests— the renderer does neither on its own.

## Saving Downloads

When a page starts a file download, you receive the incoming data and write it to disk.

### Attaching a Download Listener

To handle downloads, implement `DownloadListener` and attach it with `View::set_download_listener()`. Without a listener attached, the View ignores all downloads.

The View stores only a pointer to the listener without taking ownership— you must keep the listener alive for as long as the View uses it (see [Handling View Events](/docs/2.0/handling-view-events)).

`DownloadListener` has no default implementations— you must implement all six callbacks. The renderer calls each callback on the Renderer's thread (the thread that created the Renderer).

### When Downloads Start

A download starts when the page navigates to a file the renderer can't display.

This happens when the server sends a `Content-Disposition: attachment` header, or returns a MIME type the library can't render.

The renderer doesn't support the HTML `download` attribute on links— clicking one navigates to the file like an ordinary link.

### Handling Callbacks in Order

The renderer calls the download callbacks in order throughout the transfer.

| Callback | Action |
| :--- | :--- |
| `NextDownloadId()` | Return a unique ID on each call (an incrementing counter starting at 0 works well). |
| `OnRequestDownload()` | Return `true` to allow the download or `false` to block it. When blocked, no further callbacks fire for that ID. |
| `OnBeginDownload()` | Open a destination file on disk. The callback receives the URL, a suggested filename, and the expected file size in bytes. |
| `OnReceiveDataForDownload()` | Write incoming chunks to the file. The renderer calls this repeatedly as data arrives. |
| `OnFinishDownload()` | Close the completed file. |
| `OnFailDownload()` | Close the file and delete the partial download from disk. |

> 🚧 Do Not Keep the Buffer
>
> The `Buffer` passed to `OnReceiveDataForDownload()` is valid only during the call. Write the bytes to disk or copy them inside the callback— never keep the `Buffer` after the callback returns.

### Saving Downloads to a Folder

The following listener tracks active downloads by ID and saves incoming files into a **downloads** folder:

```cpp
#include <Ultralight/Ultralight.h>
#include <cstdio>
#include <fstream>
#include <map>
#include <string>

using namespace ultralight;

RefPtr<View> view;

class MyDownloadListener : public DownloadListener {
 public:
  ///
  /// Hand out ids in order, starting at 0.
  ///
  DownloadId NextDownloadId(View* caller) override { return next_id_++; }

  ///
  /// Allow every download (return false here to block one).
  ///
  bool OnRequestDownload(View* caller, DownloadId id,
                         const String& url) override {
    return true;
  }

  ///
  /// The View writes nothing to disk, so open a file for this download
  /// ourselves. The suggested name can be empty, so fall back to the id.
  ///
  void OnBeginDownload(View* caller, DownloadId id, const String& url,
                       const String& filename,
                       int64_t expected_content_length) override {
    std::string name = filename.utf8().data();
    if (name.empty())
      name = "download-" + std::to_string(id);

    Download& download = downloads_[id];
    download.path = "downloads/" + name;
    download.file.open(download.path, std::ios::binary);
  }

  ///
  /// Data streams in over many calls, so append each chunk to the file.
  ///
  void OnReceiveDataForDownload(View* caller, DownloadId id,
                                RefPtr<Buffer> data) override {
    downloads_[id].file.write(static_cast<const char*>(data->data()),
                              data->size());
  }

  ///
  /// Close the file when the download completes.
  ///
  void OnFinishDownload(View* caller, DownloadId id) override {
    downloads_[id].file.close();
    downloads_.erase(id);
  }

  ///
  /// On failure, close the file and delete the partial download.
  /// (This can fire before OnBeginDownload, so look the id up first.)
  ///
  void OnFailDownload(View* caller, DownloadId id) override {
    auto it = downloads_.find(id);
    if (it == downloads_.end())
      return;

    it->second.file.close();
    std::remove(it->second.path.c_str());
    downloads_.erase(it);
  }

 private:
  struct Download {
    std::string path;
    std::ofstream file;
  };

  DownloadId next_id_ = 0;
  std::map<DownloadId, Download> downloads_;
};

MyDownloadListener downloads;

void AttachDownloadListener() {
  ///
  /// Hand the View a pointer to our listener (ownership stays with us).
  ///
  view->set_download_listener(&downloads);
}
```

#### Handling the Suggested Filename

The suggested filename passed to `OnBeginDownload()` contains only the file name itself— the library strips all directory paths from the server response and the URL.

Because the suggested name can be empty, you should provide a fallback name when opening the destination file.

### Handling Download Failures

The renderer can call `OnFailDownload()` for a download that never reached `OnBeginDownload()` (eg, if the network connection drops before the server responds).

You should verify that the download ID exists before attempting to close or remove a file— if `OnBeginDownload()` never ran, no destination file was created on disk.

### Canceling a Download

To stop an active download, call `View::CancelDownload()` with its ID.

The renderer stops sending data for that download and calls `OnFailDownload()` during a later `Renderer::Update()`. You can clean up and delete the partial file in that callback just as you would for any other failure.

> 🚧 Clean Up Open Files When Releasing a View
>
> Releasing a View ends all of its active downloads without firing any callbacks— not even `OnFailDownload()`. You must close and delete any files that are still open when releasing the View.

## Filtering Network Requests

You can intercept and filter the HTTP and HTTPS requests a page sends before they reach the network.

> 📘 Free Edition Limitations
>
> The Free edition does not include a network filter, and `OnNetworkRequest()` is never called. Do not rely on network listeners to enforce security in builds compiled with the Free edition (see [Editions and Feature Macros](/docs/2.0/editions-and-feature-macros)).

### Attaching a Network Listener

To inspect and filter network requests, implement `NetworkListener` and attach it with `View::set_network_listener()`.

The View stores only a pointer to the listener without taking ownership— you must keep the listener alive for as long as the View uses it.

The renderer calls `OnNetworkRequest()` on the Renderer's thread (the thread that created the Renderer) before sending each request. The callback receives every HTTP and HTTPS request the page makes, including documents, stylesheets, scripts, images, `fetch()` calls, and asynchronous XMLHttpRequests. Requests for `file:` and `data:` URLs bypass the listener, as do synchronous XMLHttpRequests.

### Allowing or Blocking Requests

Return `true` from `OnNetworkRequest()` to allow the request to proceed. Return `false` to block the request, which causes the resource load to fail immediately on the page.

The callback receives a `NetworkRequest&` parameter describing the outgoing request. You can inspect `request.url()`, `request.urlHost()`, the HTTP method, the request origin, and the user agent (see the [NetworkRequest](/api/cpp/2_0_0/classultralight_1_1_network_request.html) reference).

### Public Key Pinning

You can enforce additional TLS validation on outgoing requests by pinning the server's public key— if the server's key does not match, the connection fails.

Call `NetworkRequest::EnforcePinnedPublicKey()` with one or more base64-encoded SHA-256 hashes. Prefix each hash with `sha256//` and separate multiple hashes with semicolons (the format used by cURL's [`CURLOPT_PINNEDPUBLICKEY`](https://curl.se/libcurl/c/CURLOPT_PINNEDPUBLICKEY.html)).

The following example restricts outgoing requests to a specific host and pins its public key:

```cpp
#include <Ultralight/Ultralight.h>

using namespace ultralight;

RefPtr<View> view;

class MyNetworkListener : public NetworkListener {
 public:
  ///
  /// Let requests to our own host through and block everything else.
  ///
  bool OnNetworkRequest(View* caller, NetworkRequest& request) override {
    if (request.urlHost() != "ultralig.ht")
      return false;

    ///
    /// Pin our server's public key (additional TLS certificate validation).
    ///
    request.EnforcePinnedPublicKey(
        "sha256//YhKJKSzoTt2b5FP18fvpHo7fJYqQCjAa3HWY3tvRMwE=");
    return true;
  }
};

MyNetworkListener network;

void AttachNetworkListener() {
  ///
  /// Hand the View a pointer to our listener (ownership stays with us).
  ///
  view->set_network_listener(&network);
}
```
