ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
webdav.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#ifndef PROTOCORE_WEBDAV_H
5#define PROTOCORE_WEBDAV_H
6
7#include "protocore_config.h" // the entry point: protocore_types.h for the widths
8
10
11/**
12 * @file webdav.h
13 * @brief WebDAV wire format (RFC 4918): method classification, header parsing,
14and the 207 Multi-Status XML builder.
15 *
16 * Mirrors the CoAP/SNMP split: this header declares the pure, host-testable core
17 * (no sockets, no filesystem - unit-tested in env:native_webdav). The
18 * filesystem-backed request handling (PROPFIND directory walk, PUT/MKCOL/DELETE/
19 * COPY/MOVE, GET via the file-serving path) lives in server/io/webdav_handler.h
20 * and runs only on a build with a real filesystem.
21 *
22 * Scope: class 1 (PROPFIND Depth 0/1, PROPPATCH, PUT, DELETE, MKCOL, COPY, MOVE)
23 * plus OPTIONS and class 2 LOCK/UNLOCK, now enforced by a small lock table (see
24 * the lock manager below): a locked resource rejects a write that does not present
25 * the matching token in its If header (423 Locked). PROPPATCH is answered 207 with every requested property refused 403
26 * (read-only live properties, no dead-property store). The filesystem-backed
27 * handler streams a PUT body straight to the file (PC's stream-body
28 * hook), so uploads are not bounded by BODY_BUF_SIZE.
29 *
30 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
31 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
32 * a caller drives every namespace the same way.
33 */
34
35// PROTOCORE_WEBDAV_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
36// it into its arena. Its size and its offset are each a static_assert, so a feature
37// combination that does not fit fails to compile rather than overrunning at run time.
38
39/** @brief Depth: infinity sentinel (a lone constant). */
40#define PROTOCORE_DAV_DEPTH_INFINITY 0x7fffffff
41
42/** @brief Maximum concurrent locks (fixed - a small structural bound, not a per-board tunable). */
43#define PROTOCORE_DAV_LOCK_MAX 8
44
45/** @brief Maximum locked-path length, including the NUL. */
46#define PROTOCORE_DAV_LOCK_PATH_MAX 128
47
48/** @brief Maximum lock-token length, including the NUL (e.g. "opaquelocktoken:xxxxxxxx-pc"). */
49#define PROTOCORE_DAV_LOCK_TOKEN_MAX 48
50
51/** @brief WebDAV request methods recognized by the server. */
68
69/** @brief One active lock (RFC 4918 §6.4). */
70typedef struct
71{
72 char path[PROTOCORE_DAV_LOCK_PATH_MAX]; ///< the locked resource path (trailing slash normalized off)
73 char token[PROTOCORE_DAV_LOCK_TOKEN_MAX]; ///< the lock token (an opaquelocktoken URI)
74 proto_bool exclusive; ///< exclusive-write (true) or shared (false)
75 proto_bool depth_infinity; ///< the lock covers the whole subtree (Depth: infinity) vs just the resource
76 proto_bool active; ///< false = free slot
77 uint32_t expiry_s; ///< monotonic second the lock expires (0 = no timeout); swept by _sweep
78} DavLock;
79
80/** @brief The server-global lock table (one instance, not per-connection). */
81typedef struct
82{
85
86/** @brief Dispatch table. Addressed by offset, so the layout is asserted below. */
87typedef struct
88{
89 WebDavMethod (*method)(uint8_t *, const char *);
90 int (*depth)(uint8_t *, const char *, int);
91 size_t (*xml_escape)(uint8_t *, char *, size_t, const char *);
92 proto_bool (*dest_path)(uint8_t *, const char *, char *, size_t);
93 size_t (*ms_begin)(uint8_t *, char *, size_t, size_t);
94 size_t (*ms_entry)(uint8_t *, char *, size_t, size_t, const char *, proto_bool, uint32_t, const char *,
95 const char *);
96 size_t (*ms_end)(uint8_t *, char *, size_t, size_t);
97 size_t (*proppatch_ms)(uint8_t *, char *, size_t, const char *, const char *, size_t);
98 void (*lock_init)(uint8_t *, DavLockTable *);
99 const DavLock *(*lock_acquire)(uint8_t *, DavLockTable *, const char *, const char *, proto_bool, proto_bool,
100 uint32_t);
101 size_t (*lock_sweep)(uint8_t *, DavLockTable *, uint32_t);
102 const DavLock *(*lock_refresh)(uint8_t *, DavLockTable *, const char *, uint32_t);
103 const DavLock *(*lock_find)(uint8_t *, const DavLockTable *, const char *);
104 proto_bool (*lock_release)(uint8_t *, DavLockTable *, const char *);
105 proto_bool (*lock_can_write)(uint8_t *, const DavLockTable *, const char *, const char *);
106 proto_bool (*if_token)(uint8_t *, const char *, char *, size_t);
107} WebdavNs;
108PROTOCORE_NS_LAYOUT(WebdavNs, method, depth, xml_escape, dest_path, ms_begin, ms_entry, ms_end, proppatch_ms, lock_init,
109 lock_acquire, lock_sweep, lock_refresh, lock_find, lock_release, lock_can_write, if_token);
110
111/**
112 * @brief Classify an HTTP method token (e.g. "PROPFIND") into a WebDavMethod.
113 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
114 * @param m M
115 * @return The WebDavMethod.
116 */
117WebDavMethod protocore_webdav_method(uint8_t *work, const char *m);
118/**
119 * @brief Parse a Depth header value ("0", "1", or "infinity").
120 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
121 * @param depth_hdr Depth hdr
122 * @param dflt Dflt
123 * @return The int.
124 */
125int protocore_webdav_depth(uint8_t *work, const char *depth_hdr, int dflt);
126/**
127 * @brief XML-escape src into dst (`&`, `<`, `>`, `"`, `'`).
128 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
129 * @param dst Dst
130 * @param cap Cap
131 * @param src Src
132 * @return The size_t.
133 */
134size_t protocore_webdav_xml_escape(uint8_t *work, char *dst, size_t cap, const char *src);
135/**
136 * @brief Extract and percent-decode the path of a Destination header. .
137 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
138 * @param destination Destination
139 * @param out Out
140 * @param cap Cap
141 * @return PROTO_TRUE on success.
142 */
143proto_bool protocore_webdav_dest_path(uint8_t *work, const char *destination, char *out, size_t cap);
144/**
145 * @brief Write the XML prolog and the open <multistatus> element.
146 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
147 * @param buf Buf
148 * @param cap Cap
149 * @param len Len
150 * @return The size_t.
151 */
152size_t protocore_webdav_ms_begin(uint8_t *work, char *buf, size_t cap, size_t len);
153/**
154 * @brief Append one <response> describing a resource.
155 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
156 * @param buf Buf
157 * @param cap Cap
158 * @param len Len
159 * @param href the resource's URL path (XML-escaped here)
160 * @param is_collection true for a directory (emits <collection/>)
161 * @param size content length (files only)
162 * @param rfc1123_mtime Last-Modified string, or "" to omit
163 * @param content_type MIME type (files only), or "" to omit
164 * @return The size_t.
165 */
166size_t protocore_webdav_ms_entry(uint8_t *work, char *buf, size_t cap, size_t len, const char *href,
167 proto_bool is_collection, uint32_t size, const char *rfc1123_mtime,
168 const char *content_type);
169/**
170 * @brief Close the <multistatus> element.
171 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
172 * @param buf Buf
173 * @param cap Cap
174 * @param len Len
175 * @return The size_t.
176 */
177size_t protocore_webdav_ms_end(uint8_t *work, char *buf, size_t cap, size_t len);
178/**
179 * @brief Build a complete 207 Multi-Status body answering a PROPPATCH. The .
180 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
181 * @param buf destination buffer (whole document, NUL-terminated)
182 * @param cap buffer capacity
183 * @param href the resource path (XML-escaped here)
184 * @param body the PROPPATCH request body (not required to be NUL-terminated)
185 * @param body_len length of body
186 * @return The size_t.
187 */
188size_t protocore_webdav_proppatch_ms(uint8_t *work, char *buf, size_t cap, const char *href, const char *body,
189 size_t body_len);
190/**
191 * @brief Reset a lock table (no locks held).
192 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
193 * @param t T
194 */
196/**
197 * @brief Acquire a lock on path with the caller-supplied token (RFC 4918 .
198 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
199 * @param t T
200 * @param path Path
201 * @param token Token
202 * @param exclusive Exclusive
203 * @param depth_infinity Depth infinity
204 * @param expiry_s the monotonic second the lock expires (0 = no timeout); protocore_dav_lock_sweep drops a
205 * @return The const DavLock *.
206 */
207const DavLock *protocore_webdav_lock_acquire(uint8_t *work, DavLockTable *t, const char *path, const char *token,
208 proto_bool exclusive, proto_bool depth_infinity, uint32_t expiry_s);
209/**
210 * @brief Expire and drop every lock whose timeout has passed (RFC 4918 .
211 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
212 * @param t T
213 * @param now_s the caller's current monotonic second
214 * @return The size_t.
215 */
216size_t protocore_webdav_lock_sweep(uint8_t *work, DavLockTable *t, uint32_t now_s);
217/**
218 * @brief Refresh a held lock's timeout to new_expiry_s, keyed by token (a .
219 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
220 * @param t T
221 * @param token Token
222 * @param new_expiry_s New expiry s
223 * @return The const DavLock *.
224 */
225const DavLock *protocore_webdav_lock_refresh(uint8_t *work, DavLockTable *t, const char *token, uint32_t new_expiry_s);
226/**
227 * @brief Find a lock covering path: one on path itself, or a Depth-infinity .
228 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
229 * @param t T
230 * @param path Path
231 * @return The const DavLock *.
232 */
233const DavLock *protocore_webdav_lock_find(uint8_t *work, const DavLockTable *t, const char *path);
234/**
235 * @brief Release the lock whose token equals token (UNLOCK). true if one was .
236 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
237 * @param t T
238 * @param token Token
239 * @return PROTO_TRUE on success.
240 */
241proto_bool protocore_webdav_lock_release(uint8_t *work, DavLockTable *t, const char *token);
242/**
243 * @brief May a write to path proceed given the token the request presented .
244 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
245 * @param t T
246 * @param path Path
247 * @param presented_token Presented token
248 * @return PROTO_TRUE on success.
249 */
250proto_bool protocore_webdav_lock_can_write(uint8_t *work, const DavLockTable *t, const char *path,
251 const char *presented_token);
252/**
253 * @brief Extract the first lock token from an If header value (RFC 4918 .
254 * @param work PROTOCORE_WEBDAV_BORROW bytes the caller took. Not held past the call.
255 * @param if_header If header
256 * @param out Out
257 * @param cap Cap
258 * @return PROTO_TRUE on success.
259 */
260proto_bool protocore_webdav_if_token(uint8_t *work, const char *if_header, char *out, size_t cap);
261
262/** @brief Module namespace. */
264 .depth = protocore_webdav_depth,
265 .xml_escape = protocore_webdav_xml_escape,
266 .dest_path = protocore_webdav_dest_path,
267 .ms_begin = protocore_webdav_ms_begin,
268 .ms_entry = protocore_webdav_ms_entry,
269 .ms_end = protocore_webdav_ms_end,
270 .proppatch_ms = protocore_webdav_proppatch_ms,
271 .lock_init = protocore_webdav_lock_init,
272 .lock_acquire = protocore_webdav_lock_acquire,
273 .lock_sweep = protocore_webdav_lock_sweep,
274 .lock_refresh = protocore_webdav_lock_refresh,
275 .lock_find = protocore_webdav_lock_find,
276 .lock_release = protocore_webdav_lock_release,
277 .lock_can_write = protocore_webdav_lock_can_write,
278 .if_token = protocore_webdav_if_token};
279
281
282#endif // PROTOCORE_WEBDAV_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_NS_LAYOUT(T,...)
Pin every dispatch slot of a table that is nothing but function pointers.
#define PROTOCORE_NS
Storage for a dispatch table. The const is load bearing.
The server-global lock table (one instance, not per-connection).
Definition webdav.h:82
One active lock (RFC 4918 §6.4).
Definition webdav.h:71
proto_bool active
false = free slot
Definition webdav.h:76
uint32_t expiry_s
monotonic second the lock expires (0 = no timeout); swept by _sweep
Definition webdav.h:77
proto_bool depth_infinity
the lock covers the whole subtree (Depth: infinity) vs just the resource
Definition webdav.h:75
proto_bool exclusive
exclusive-write (true) or shared (false)
Definition webdav.h:74
Dispatch table. Addressed by offset, so the layout is asserted below.
Definition webdav.h:88
WebDavMethod(* method)(uint8_t *, const char *)
Definition webdav.h:89
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97
proto_bool protocore_webdav_if_token(uint8_t *work, const char *if_header, char *out, size_t cap)
Extract the first lock token from an If header value (RFC 4918 .
size_t protocore_webdav_lock_sweep(uint8_t *work, DavLockTable *t, uint32_t now_s)
Expire and drop every lock whose timeout has passed (RFC 4918 .
#define PROTOCORE_DAV_LOCK_PATH_MAX
Maximum locked-path length, including the NUL.
Definition webdav.h:46
#define PROTOCORE_DAV_LOCK_TOKEN_MAX
Maximum lock-token length, including the NUL (e.g. "opaquelocktoken:xxxxxxxx-pc").
Definition webdav.h:49
const DavLock * protocore_webdav_lock_acquire(uint8_t *work, DavLockTable *t, const char *path, const char *token, proto_bool exclusive, proto_bool depth_infinity, uint32_t expiry_s)
Acquire a lock on path with the caller-supplied token (RFC 4918 .
size_t protocore_webdav_ms_end(uint8_t *work, char *buf, size_t cap, size_t len)
Close the <multistatus> element.
size_t protocore_webdav_ms_entry(uint8_t *work, char *buf, size_t cap, size_t len, const char *href, proto_bool is_collection, uint32_t size, const char *rfc1123_mtime, const char *content_type)
Append one <response> describing a resource.
const DavLock * protocore_webdav_lock_refresh(uint8_t *work, DavLockTable *t, const char *token, uint32_t new_expiry_s)
Refresh a held lock's timeout to new_expiry_s, keyed by token (a .
#define PROTOCORE_DAV_LOCK_MAX
Maximum concurrent locks (fixed - a small structural bound, not a per-board tunable).
Definition webdav.h:43
size_t protocore_webdav_ms_begin(uint8_t *work, char *buf, size_t cap, size_t len)
Write the XML prolog and the open <multistatus> element.
void protocore_webdav_lock_init(uint8_t *work, DavLockTable *t)
Reset a lock table (no locks held).
int protocore_webdav_depth(uint8_t *work, const char *depth_hdr, int dflt)
Parse a Depth header value ("0", "1", or "infinity").
const DavLock * protocore_webdav_lock_find(uint8_t *work, const DavLockTable *t, const char *path)
Find a lock covering path: one on path itself, or a Depth-infinity .
@ DAV_M_OPTIONS
Definition webdav.h:54
@ DAV_M_PROPPATCH
Definition webdav.h:60
@ DAV_M_UNLOCK
Definition webdav.h:65
@ DAV_M_MKCOL
Definition webdav.h:61
@ DAV_M_PROPFIND
Definition webdav.h:59
@ DAV_M_PUT
Definition webdav.h:57
@ DAV_M_LOCK
Definition webdav.h:64
@ DAV_M_DELETE
Definition webdav.h:58
@ DAV_M_MOVE
Definition webdav.h:63
@ DAV_M_UNSUPPORTED
Anything else - answered 405 Method Not Allowed.
Definition webdav.h:66
@ DAV_M_GET
Definition webdav.h:55
@ DAV_M_HEAD
Definition webdav.h:56
@ DAV_M_COPY
Definition webdav.h:62
size_t protocore_webdav_proppatch_ms(uint8_t *work, char *buf, size_t cap, const char *href, const char *body, size_t body_len)
Build a complete 207 Multi-Status body answering a PROPPATCH. The .
WebDavMethod protocore_webdav_method(uint8_t *work, const char *m)
Classify an HTTP method token (e.g. "PROPFIND") into a WebDavMethod.
PROTOCORE_NS WebdavNs Webdav PROTOCORE_UNUSED
Module namespace.
Definition webdav.h:263
proto_bool protocore_webdav_lock_release(uint8_t *work, DavLockTable *t, const char *token)
Release the lock whose token equals token (UNLOCK). true if one was .
enum PROTO_ENUM_PACKED WebDavMethod
WebDAV request methods recognized by the server.
proto_bool protocore_webdav_lock_can_write(uint8_t *work, const DavLockTable *t, const char *path, const char *presented_token)
May a write to path proceed given the token the request presented .
proto_bool protocore_webdav_dest_path(uint8_t *work, const char *destination, char *out, size_t cap)
Extract and percent-decode the path of a Destination header. .
size_t protocore_webdav_xml_escape(uint8_t *work, char *dst, size_t cap, const char *src)
XML-escape src into dst (&, <, >, ", ‘’`).