Network operations
A plugin has no sockets of its own. Every connection is opened by the app, after the host checked the permission, the address and the limits described here. For the envelope, async mechanism and the common limits table see System operations. The user guide is Network.
Permissions
Section titled “Permissions”| Permission | Allows |
|---|---|
net.https |
net.fetch to public https:// URLs |
net.ws |
net.ws.connect to public wss:// URLs |
net.tcp |
net.tcp.connect to public hosts (optionally with TLS) |
net.udp |
net.udp.connect to public hosts |
net.local |
the same calls to this computer only (loopback), and only to the ports listed in the manifest ports field |
net.local takes ports in the manifest: 1 to 16 entries, each an integer (8080) or a text range ("9000-9010"), values 1 to 65535. A loopback target whose port is not listed is denied “this port”. net.local does not reach the LAN, only the loopback host names below.
Permission and address are decided per call. For an already open socket the permission is checked again on every net.socket.* / net.ws.* call (denied “socket” if it was revoked).
Host policy (net_policy)
Section titled “Host policy (net_policy)”Public targets
Section titled “Public targets”For a public target (everything except the loopback targets below) the host accepts:
| Rule | Detail |
|---|---|
| Scheme | https:// for net.fetch, wss:// for net.ws.connect. http:// and ws:// are refused (invalid “only https URLs are allowed” / “url must start with wss://”) |
| URL | at most 2048 bytes; only ASCII from 0x21 to 0x7E (no spaces, no control or non-ASCII characters; use percent-encoding and punycode); fragment is dropped |
| User info | a URL with @ in the authority is refused: invalid “the URL has no usable host” |
| Port | 1 to 65535, any port; default 443. An IPv6 address must be in brackets |
| Host name | at most 253 characters, labels up to 63 of letters, digits, -, _; case-insensitive; trailing dots removed; a last label that starts with a digit is refused (blocks 127.1, 0x7f.1 style tricks): invalid “a bad host name” |
| Refused names | localhost, *.localhost, *.local, *.internal, *.home.arpa: invalid “local hosts are not allowed” |
| IP literals | must be public: refused with invalid “the address is not public” for 0.0.0.0/8, 10/8, 100.64/10, 127/8, 169.254/16, 172.16/12, 192.0.0/24, 192.0.2/24, 192.168/16, 198.18/15, 198.51.100/24, 203.0.113/24, multicast and above (224/4 and up); IPv6 ::, ::1, fc00::/7, ff00::/8, fe80::/10, fec0::/10, 2001:db8::/32, 100::/64, 64:ff9b:1::/48; IPv4-mapped (::ffff:a.b.c.d), IPv4-compatible, NAT64 (64:ff9b::/96), 6to4 (2002::/16) and Teredo (2001::/32) addresses are judged by the IPv4 address they contain |
DNS and rebinding
Section titled “DNS and rebinding”For a host name the app resolves it itself and applies the rule to every address of the answer: if any address is not public the call fails with transport code blocked (“the address is not public”). The connection then goes to the first address of that same answer, so the name is not resolved a second time between the check and the connection (no DNS rebinding window). Every redirect hop is checked again from scratch.
With a proxy configured in the app (see below) the name is sent to the proxy and resolved there; the app does not see the address, so the DNS-answer check cannot be applied. IP literals are still checked before the call.
Loopback targets (net.local)
Section titled “Loopback targets (net.local)”A target is local when the host is localhost, an address in 127.0.0.0/8, or ::1 ([::1] in URLs); a trailing dot is ignored. Local targets:
- need
net.local(denied“net.local”) instead of the public permission, and an allowed port; - for
net.fetch, may usehttp://orhttps://(default ports 80 and 443);ws://andwss://fornet.ws.connect; any other scheme isinvalid; - are never sent through the proxy and never follow redirects;
- at the transport level resolve only to loopback addresses (
blockedotherwise).
net.fetch
Section titled “net.fetch”Permission net.https (public) or net.local (loopback). Async supported.
| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
url |
text | yes | rules above; invalid “method or url” if missing/too long, or the parser’s message |
|
method |
text | no | "GET" |
exactly GET, POST, PUT, PATCH, DELETE or HEAD (case sensitive); other text of up to 8 bytes is invalid “method or url”. A value that is not text, or is longer than 8 bytes, is silently replaced by GET |
headers |
map text to text | no | none | up to 32 entries, see below |
body |
bytes, text or null |
no | none | text is sent as UTF-8; other types invalid “body”; over 100 MiB limit “the body is too large” |
timeout |
integer, ms | no | 10000 | clamped to 100 to 300000; a non-integer is ignored (10000) |
Result:
{"status": 200, "headers": {"content-type": "text/html", ...}, # lower-case names, repeated names joined with ", " "header_list": [["content-type", "text/html"], ...], # every header in order, lower-case names "body": <bytes>}Set-Cookie and Set-Cookie2 are removed from both headers and header_list. HTTP error statuses (4xx, 5xx) are a normal result, not an error. In Python, net.fetch returns Response (status, headers, header_list, body, text(), json(), ok); net.get and net.post are shortcuts.
r = freshgram.net.fetch("https://example.org/api", method="POST", headers={"Content-Type": "application/json"}, body=b"{}")# raw: {"op": "net.fetch", "a": {"url": "https://example.org/api", "method": "POST",# "headers": {"Content-Type": "application/json"}, "body": <bytes>, "timeout": 10000}}Request headers
Section titled “Request headers”| Rule | Detail |
|---|---|
| Count and size | at most 32 headers, value at most 4096 bytes, name 1 to 64 characters |
| Name | letters, digits and the symbols ! # $ % & ' * + - . ^ _ ~, the backtick and the vertical bar |
| Value | text; no control characters except tab (no CR, LF) |
| Forbidden names | Host, Content-Length, Transfer-Encoding, Connection, Upgrade, TE, Trailer, Expect, Cookie, Cookie2, Proxy-Authorization, Proxy-Connection, Keep-Alive, Via, Forwarded, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, X-Real-IP, anything starting with Proxy- or Sec- (case-insensitive) |
A header that breaks a rule is invalid “header Host, Connection: close, Accept-Encoding: identity itself, adds Content-Length for bodies and for POST/PUT/PATCH, and User-Agent: freshGram-plugin unless you set User-Agent. Responses are never decompressed (the request asks for identity).
Redirects
Section titled “Redirects”Only for public targets. Up to 5 hops, statuses 301, 302, 303, 307, 308 with a Location header (relative locations are resolved). The target must be https:// and pass all the address rules; otherwise the call fails with redirect “a redirect is not allowed”; more than 5: redirect “too many redirects”. 303, and 301/302 after POST, switch to GET with no body; 307/308 keep method and body. The Authorization header is dropped when the host changes. A redirect status without Location is returned as the result. Local requests do not follow redirects (the 3xx answer is the result). One deadline (timeout) covers the whole chain.
Size and time
Section titled “Size and time”| Limit | Value |
|---|---|
| Response body | 100 MiB (limit “the response is too large”; also checked early from Content-Length and while decoding chunks) |
| Response header block | 64 KiB (response “the headers are too large”) |
| Request body | 100 MiB |
timeout |
whole request including connect, TLS, redirects: timeout “the request timed out” |
The body is read by Content-Length, by chunked decoding, or until the server closes the connection. A malformed answer is response. Note that a HEAD reply that carries a Content-Length makes the host wait for body bytes that never come, so it ends with response “the connection closed early”; prefer GET when you need to inspect such a resource.
The host speaks TLS 1.2 or newer, verifies the certificate chain against the system trust store and checks that the certificate matches the host name from the URL. There is no way to disable verification or pin a certificate. The same applies to tls=True TCP sockets and wss://. A failure is tls.
Errors
Section titled “Errors”| Code | Trigger |
|---|---|
unavailable “network” |
the app provides no network service |
invalid |
bad method/URL/headers/body (see tables) |
denied |
net.https / net.local not granted; denied “this port” for a local port not in ports |
limit |
body or response too large; rate limits of the transport |
| transport codes | see below |
net.tcp.connect, net.udp.connect
Section titled “net.tcp.connect, net.udp.connect”Permission net.tcp / net.udp, or net.local when host is a loopback host. Async supported (runs on the socket thread).
| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
host |
text | yes | up to 255 bytes, public host rules (IP literal or name); loopback hosts switch to the net.local rules |
|
port |
integer | yes | 1 to 65535; for loopback it must also be in the manifest ports, else denied “this port” |
|
tls |
bool | no | false |
TCP only. true on UDP or a non-bool value: invalid “tls” |
timeout |
integer, ms | no | 10000 | clamped to 0 to 30000, then at least 100 (applies to the connection and TLS handshake); a non-integer is ignored (10000) |
Result: {"id": <socket id>}. Socket ids are shared by TCP, UDP and WebSocket. A tls=True connection verifies the certificate against host. At most 16 open sockets per kind: limit “too many open sockets”. A UDP socket is bound to a single peer: it sends to that peer and only receives datagrams that come from it (and the relay when proxied).
s = freshgram.net.tcp("example.org", 443, tls=True) # -> Socket# raw: {"op": "net.tcp.connect", "a": {"host": "example.org", "port": 443, "tls": true, "timeout": 10000}}Errors: denied (permission, port), invalid (“host or port”, “tls”, policy messages such as “the address is not public”), limit, and the transport codes.
net.socket.send, net.socket.recv, net.socket.close
Section titled “net.socket.send, net.socket.recv, net.socket.close”Apply to TCP and UDP sockets. A WebSocket id used here is invalid “this socket does not take that call”; an unknown or closed id is not_found “socket” (the same for net.ws.send, net.ws.recv and net.ws.close).
net.socket.send
Section titled “net.socket.send”| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
id |
integer | yes | open socket | |
data |
bytes or text | yes | text is sent as UTF-8; not empty; up to 1 MiB (TCP) or 65507 bytes (UDP); else limit “the data is too large or empty”; wrong type invalid “data” |
|
timeout |
integer, ms | no | 10000 | clamped to 0 to 30000, then at least 100; a non-integer is ignored (10000) |
Result: {"sent": <bytes>}. UDP is limited to 200 datagrams per second per socket: limit “too many datagrams”.
net.socket.recv
Section titled “net.socket.recv”| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
id |
integer | yes | ||
max |
integer | no | 65507 | clamped to 1 to 1048576; a non-integer is ignored (65507) |
timeout |
integer, ms | no | 1000 | clamped to 0 to 30000; a non-integer is ignored (1000) |
Result: {"data": <bytes>, "closed": <bool>}. For TCP it waits until at least one byte is available, the connection closes or the timeout passes; data is empty when nothing arrived. closed is true once the peer closed and nothing is left to read. For UDP it returns one datagram (truncated to max); datagrams from other senders are discarded.
data, closed = s.recv(max_bytes=4096, timeout=1000)# raw: {"op": "net.socket.recv", "a": {"id": 5, "max": 4096, "timeout": 1000}}net.socket.close
Section titled “net.socket.close”Argument id. Result null. The id becomes invalid. Sockets are also closed when the plugin stops, and the user can close any connection from the app (later calls then fail with closed).
net.ws.connect, net.ws.send, net.ws.recv, net.ws.close
Section titled “net.ws.connect, net.ws.send, net.ws.recv, net.ws.close”Permission net.ws (public wss://), or net.local for ws:///wss:// to loopback. Async supported.
net.ws.connect
Section titled “net.ws.connect”| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
url |
text | yes | up to 2048 bytes; a missing, non-text or longer URL is invalid “url”; wss:// for public hosts (the URL rules above); invalid “url must start with wss://” otherwise |
|
protocols |
array of text | no | none | up to 8 names, each 1 to 64 of letters, digits, . _ - +; else invalid “protocols” |
headers |
map | no | none | the same rules and forbidden names as net.fetch; Sec-* are forbidden |
timeout |
integer, ms | no | 10000 | clamped to 100 to 30000 (connect, TLS and handshake together); a non-integer is ignored (10000) |
Result: {"id": <socket id>, "protocol": "<chosen subprotocol or empty>"}. The handshake must return 101 with a valid Sec-WebSocket-Accept, Upgrade: websocket and Connection: Upgrade; the host accepts no extensions (so no compression) and no subprotocol that was not offered; handshake response up to 16 KiB. Failures are failed with a message such as “the server refused: HTTP/1.1 403”, “the server chose an extension”. At most 16 open WebSockets.
net.ws.send
Section titled “net.ws.send”| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
id |
integer | yes | a WebSocket id | |
data |
text or bytes | yes | text goes as a text frame, bytes as a binary frame; up to 1 MiB else limit “the message is too large”; wrong type invalid “data” |
|
timeout |
integer, ms | no | 10000 | clamped to 0 to 30000, then at least 100; a non-integer is ignored (10000) |
Result: {"sent": <bytes>}. After a close was sent or received: failed “the connection is closed”.
net.ws.recv
Section titled “net.ws.recv”| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
id |
integer | yes | ||
timeout |
integer, ms | no | 1000 | clamped to 0 to 30000; a non-integer is ignored (1000) |
Result is one of:
kind |
Other fields | Meaning |
|---|---|---|
"text" |
data (text), closed: false |
a text message (must be valid UTF-8, otherwise the host closes with 1007 and answers failed) |
"binary" |
data (bytes), closed: false |
a binary message |
"close" |
code, reason, closed: true |
the server closed (code 1005 when no code was sent; 1006 when the connection dropped) |
"none" |
closed: false |
nothing arrived within timeout |
The host answers pings itself and echoes the close. A server frame over 1 MiB, a fragmented message that grows over 1 MiB (limit “the message is too large”, close 1009), masked or reserved-bit frames, or a bad frame order are protocol errors (failed “the server broke the protocol”, close 1002).
net.ws.close
Section titled “net.ws.close”| Argument | Type | Required | Default | Validation |
|---|---|---|---|---|
id |
integer | yes | ||
code |
integer | no | 1000 | 1000 or 3000 to 4999, else invalid “code or reason” |
reason |
text | no | "" |
up to 123 bytes |
Result null. The socket id becomes invalid.
ws = freshgram.net.ws("wss://example.org/feed", protocols=["v1"])msg = ws.recv(timeout=1000)ws.send("hello")ws.close()# raw: {"op": "net.ws.recv", "a": {"id": 3, "timeout": 1000}}Proxy and user policy
Section titled “Proxy and user policy”The user can set a network policy in the app. It is applied by the transport to everything a plugin opens:
| Setting | Effect |
|---|---|
| Block HTTPS / TCP / UDP | calls of that kind fail with blocked (“… is blocked for plugins”); open connections of the kind are cut. net.ws and tls=True sockets count as TCP |
| Proxy (SOCKS5 or HTTP CONNECT, optional login) | public TCP, TLS, WebSocket and net.fetch traffic goes through it, the host name is passed to the proxy; UDP works through SOCKS5 (UDP ASSOCIATE) and fails with unavailable “UDP is not supported through an HTTP proxy” for an HTTP proxy. Loopback traffic ignores the proxy. Proxy errors are connect with a “proxy: …” message |
| Connection list / log | every request and socket is shown to the user and can be closed |
Error codes of the transport
Section titled “Error codes of the transport”These are produced by the app while connecting and transferring; in Python they all become Failed (read e.code), except invalid, limit and unavailable, which have their own classes.
| Code | Meaning |
|---|---|
blocked |
user policy blocks that kind of traffic, or an address is not public (or not loopback for local targets) |
dns |
the host name was not found |
connect |
connection or proxy failure, refused, unreachable, connect timeout |
tls |
TLS handshake failed, certificate rejected, handshake timed out |
timeout |
the request or a send did not finish in time |
closed |
the connection was closed (by the peer, the user, or on plugin stop) |
write |
the send failed |
response |
malformed HTTP answer, oversized headers, early close |
redirect |
redirect refused or too many redirects |
limit |
too many datagrams, or an HTTP body over the limit |
invalid |
a bad address or URL found while connecting |
unavailable |
e.g. UDP through an HTTP proxy |
Both net.socket.recv and net.ws.recv use the timeout only to wait; a timeout is not an error (empty data / kind: "none").
