blob: 857a6331626ef0aa08a473dfbf032eb57231ffb9 [file]
/*
* Copyright 2026 Rive
*/
#pragma once
#include "rive/scriptnet/http.hpp"
#include <string>
namespace rive::scriptnet
{
/// Host-configurable ceilings. Requests past the in-flight or rate limit wait
/// in their VM's queue and start in order, as a browser holds requests past
/// its connection limit; only a full queue, the policy or the size limits
/// reject one.
struct NetLimits
{
/// Requests one VM may have with the transport at once.
uint32_t maxInFlightPerOwner = 8;
/// Requests one VM may start in any 60 second window, 0 for no limit.
uint32_t maxRequestsPerMinutePerOwner = 0;
/// Requests one VM may have waiting, and the request body bytes they may
/// hold together; past either, a request is rejected.
uint32_t maxQueuedPerOwner = 1024;
uint32_t maxQueuedBodyBytesPerOwner = 32 * 1024 * 1024;
/// Answers one VM may have that poll() has yet to deliver, counting those
/// in flight, and their body bytes; while either is reached, its requests
/// wait to start. A body's size is known only once it arrives, so the
/// bytes held can exceed the cap by what is in flight, at most
/// maxInFlightPerOwner times maxResponseBodyBytes.
uint32_t maxUndeliveredPerOwner = 64;
uint32_t maxUndeliveredBytesPerOwner = 64 * 1024 * 1024;
uint32_t maxRequestBodyBytes = 8 * 1024 * 1024;
uint32_t maxResponseBodyBytes = 16 * 1024 * 1024;
uint32_t defaultTimeoutMs = 30 * 1000;
uint32_t maxTimeoutMs = 60 * 1000;
/// How long past its own timeout a request may go unanswered before
/// poll() stops waiting on the provider. Providers enforce the timeout
/// themselves; this only catches one that never answers.
uint32_t watchdogGraceMs = 5 * 1000;
uint32_t maxUrlLength = 8 * 1024;
uint32_t maxHeaderCount = 64;
uint32_t maxHeaderBytes = 16 * 1024;
/// Hops a transport that follows redirects itself may take for one
/// request, as a browser allows.
uint32_t maxRedirects = 20;
/// Lets a host resolve to a local or private address, which a transport
/// that checks addresses (Windows) refuses otherwise. For tests against a
/// server on loopback; off everywhere else.
bool allowLocalAddresses = false;
};
/// The rules every script request passes before a transport sees it, and
/// again on every redirect a transport follows. The transport parses the
/// URL a second time with its own parser, so this one accepts only a
/// conservative subset: anything it can't vouch for is refused.
///
/// The rules judge the host by its name. What it resolves to is each
/// transport's to check, through checkAddress(), on the address it is about
/// to connect to for every hop: Windows does so from WinHTTP's connecting
/// callback, so a public name whose DNS points at a local or private
/// address (localtest.me, 10.0.0.1.nip.io, an intranet name) is refused
/// before the request is sent, as a browser's private network access check
/// refuses it, and a connection through a proxy is the proxy's to judge.
/// Known gap, to be closed later: the Apple transport does not check yet.
/// There, since only https: is allowed, the server must still hold a valid
/// certificate for that name, which rules out routers and most local
/// devices but not intranet sites with public certificates, and a failed
/// connection still tells a script whether something answered.
class NetPolicy
{
public:
/// Normalizes `request` in place (method upper-cased, host lower-cased,
/// fragment dropped, timeout defaulted and clamped). Returns false with
/// `error` set when the request must not be sent.
static bool check(
HttpRequest& request,
const NetLimits& limits,
NetError* error);
/// For a transport that follows redirects itself. Carries `request`, as
/// last sent, to the hop that a `status` (301, 302, 303, 307 or 308)
/// response with `location` asks for, the way the Fetch standard does:
/// the Location, percent-encoded where it is not already ASCII, is
/// resolved against the request's URL; a 303, or a 301
/// or 302 answering a POST, continues as a GET without the body and its
/// Content-* headers; and Authorization is dropped once the hop leaves
/// the request's origin. The hop then passes check(). Returns false,
/// leaving `request` as it was, with `error` set when the hop must not
/// be sent: the `hops` already taken reach limits.maxRedirects, the
/// Location cannot be resolved, or check() refuses it.
static bool redirect(
HttpRequest& request,
uint16_t status,
const std::string& location,
uint32_t hops,
const NetLimits& limits,
NetError* error);
/// The host rules alone, on a lower-cased host without a port. Refuses
/// IP literals, single-label and local-network names, and every host
/// Rive operates.
static bool checkHost(const std::string& host, std::string* reason);
/// The address rules, for a transport about to connect to `address`, 4
/// bytes for IPv4 or 16 for IPv6 in network order, for a host the name
/// rules let through. Refuses loopback, link-local, private (RFC 1918,
/// the shared CGNAT space, unique local), multicast, unspecified,
/// documentation and other reserved ranges, and an IPv6 address carrying
/// an IPv4 one (mapped, NAT64, 6to4) by that address.
static bool checkAddress(
const uint8_t* address,
size_t length,
std::string* reason);
/// Names scripts may not set: the Fetch standard's forbidden request
/// headers plus the method-override family. Authorization is allowed,
/// since an author's own bearer token is legitimate. Case-insensitive.
static bool isForbiddenHeader(const std::string& name);
};
} // namespace rive::scriptnet