Skip to content

feat: Add WebDAV Class 1 server for SD card access#1030

Closed
dexif wants to merge 2 commits intocrosspoint-reader:masterfrom
dexif:feature/webdav
Closed

feat: Add WebDAV Class 1 server for SD card access#1030
dexif wants to merge 2 commits intocrosspoint-reader:masterfrom
dexif:feature/webdav

Conversation

@dexif
Copy link
Contributor

@dexif dexif commented Feb 20, 2026

Summary

PR for issue #439

Implement WebDAV support so the device can be mounted as a network drive in Windows Explorer, macOS Finder, Linux Nautilus, and mobile file managers.

Methods: OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, MOVE, COPY, LOCK, UNLOCK. All handled via onNotFound() to avoid conflicts with existing routes.

Additional Context

The WebServer library is vendored into lib/WebServer/ because the framework version buffers the entire PUT body in RAM before calling the handler. On ESP32-C3 (~150KB free heap) this makes file uploads over ~100KB impossible. The vendored copy has a minimal patch in Parsing.cpp (~14 lines) that skips body buffering for PUT requests with no registered handler, allowing the WebDAV handler to stream the body from the TCP buffer in 4KB chunks.

  Implement WebDAV support so the device can be mounted as a network
  drive in Windows Explorer, macOS Finder, Linux Nautilus, and mobile
  file managers.

  Methods: OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, MOVE,
  COPY, LOCK, UNLOCK. All handled via onNotFound() to avoid conflicts
  with existing routes.

  The WebServer library is vendored into lib/WebServer/ because the
  framework version buffers the entire PUT body in RAM before calling
  the handler. On ESP32-C3 (~150KB free heap) this makes file uploads
  over ~100KB impossible. The vendored copy has a minimal patch in
  Parsing.cpp (~14 lines) that skips body buffering for PUT requests
  with no registered handler, allowing the WebDAV handler to stream
  the body from the TCP buffer in 4KB chunks.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

A new WebServer library for ESP32 is introduced with full HTTP request parsing, routing, authentication, and file upload support. Additionally, WebDAV protocol support is integrated into the application with handlers for standard WebDAV operations on the filesystem.

Changes

Cohort / File(s) Summary
WebServer Library Core
lib/WebServer/library.json, lib/WebServer/src/WebServer.h, lib/WebServer/src/WebServer.cpp, lib/WebServer/src/HTTP_Method.h
New WebServer library (v2.0.0) with constructor/lifecycle methods, request routing via on() and addHandler(), HTTP authentication (basic/digest), response streaming with chunked transfer support, and header/argument management.
HTTP Request Processing
lib/WebServer/src/Parsing.cpp, lib/WebServer/src/Uri.h
Request line and header parsing from WiFiClient with timeout handling; form data and multipart parsing with file upload streaming; URL decoding and argument extraction; URI abstraction class supporting custom matching strategies.
Request Handler Chain
lib/WebServer/src/detail/RequestHandler.h, lib/WebServer/src/detail/RequestHandlersImpl.h
Chain-of-responsibility handler base class with concrete implementations: FunctionRequestHandler for URI+method routing with path arguments, and StaticRequestHandler for filesystem-based static file serving with MIME type resolution.
URI Matching Strategies
lib/WebServer/src/uri/UriBraces.h, lib/WebServer/src/uri/UriGlob.h, lib/WebServer/src/uri/UriRegex.h
Specialized URI matching: brace-style path parameters ({param}), glob patterns via fnmatch, and regex-based capture groups with dynamic path argument extraction.
MIME Type Resolution
lib/WebServer/src/detail/mimetable.h, lib/WebServer/src/detail/mimetable.cpp
Static PROGMEM-based lookup table mapping file extensions to MIME types for content-type headers.
WebDAV Integration
src/network/WebDAVHandler.h, src/network/WebDAVHandler.cpp, src/network/CrossPointWebServer.h, src/network/CrossPointWebServer.cpp
WebDAV protocol handler implementing OPTIONS, PROPFIND, GET, HEAD, PUT, DELETE, MKCOL, MOVE, COPY, LOCK, UNLOCK with filesystem operations, path normalization, depth/overwrite semantics, and EPUB cache invalidation hooks.
Configuration
platformio.ini
Minor library dependency additions.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant WebServer
    participant Parser as Request Parser
    participant Handler as Request Handler
    participant FS as File System

    Client->>WebServer: TCP connection (handleClient)
    WebServer->>Parser: _parseRequest(WiFiClient)
    Parser->>Parser: Read request line
    Parser->>Parser: Parse headers
    Parser->>Handler: Find handler via URI/method
    
    alt POST/PUT with form data
        Parser->>Parser: Parse multipart boundary
        Parser->>FS: Stream file upload with buffering
        FS-->>Parser: Write confirmation
    else GET with query string
        Parser->>Parser: Parse URL arguments
    end
    
    Parser-->>WebServer: Request complete
    WebServer->>Handler: Execute handler function
    Handler->>FS: Read static file (optional)
    FS-->>Handler: File stream
    Handler->>WebServer: Send response with headers
    WebServer->>Client: HTTP response with content
Loading
sequenceDiagram
    participant Client
    participant WebServer
    participant DAV as WebDAVHandler
    participant FS as File System

    Client->>WebServer: WebDAV request (PROPFIND/PUT/DELETE)
    WebServer->>DAV: handleRequest()
    DAV->>DAV: Route by HTTP method

    alt PROPFIND
        DAV->>FS: List directory contents
        FS-->>DAV: Directory entries
        DAV->>DAV: Build multistatus XML response
    else PUT (upload)
        DAV->>FS: Write file stream
        FS-->>DAV: Write confirmation
        DAV->>DAV: Clear EPUB cache if needed
    else DELETE
        DAV->>FS: Remove file/empty directory
        FS-->>DAV: Deletion confirmation
    end
    
    DAV-->>WebServer: Response ready
    WebServer->>Client: HTTP response (200/207/403/404/etc)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main addition: WebDAV Class 1 server for SD card access, which aligns with the primary feature introduced in the pull request.
Description check ✅ Passed The description comprehensively explains the WebDAV implementation, the methods supported, the reason for vendoring the WebServer library, and the specific optimization made to support streaming uploads on memory-constrained devices.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
lib/WebServer/src/WebServer.h (1)

216-216: Align the header-guard end comment with the actual guard.

Minor nit, but it avoids confusion during maintenance.

🧹 Suggested fix
-#endif //ESP8266WEBSERVER_H
+#endif //WEBSERVER_H
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/WebServer.h` at line 216, The closing `#endif` comment in
WebServer.h currently reads "//ESP8266WEBSERVER_H" but doesn't match the actual
header guard macro; open WebServer.h, locate the top guard macro (the
`#ifndef/`#define symbol used for the include guard) and update the trailing
comment on the final `#endif` to exactly match that macro name (e.g., change the
comment to the same identifier found at the top) so the end-comment aligns with
the real guard (reference the macro symbol at the top of WebServer.h and the
existing trailing comment "ESP8266WEBSERVER_H").
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22b96ec and c3fe996.

📒 Files selected for processing (17)
  • lib/WebServer/library.json
  • lib/WebServer/src/HTTP_Method.h
  • lib/WebServer/src/Parsing.cpp
  • lib/WebServer/src/Uri.h
  • lib/WebServer/src/WebServer.cpp
  • lib/WebServer/src/WebServer.h
  • lib/WebServer/src/detail/RequestHandler.h
  • lib/WebServer/src/detail/RequestHandlersImpl.h
  • lib/WebServer/src/detail/mimetable.cpp
  • lib/WebServer/src/detail/mimetable.h
  • lib/WebServer/src/uri/UriBraces.h
  • lib/WebServer/src/uri/UriGlob.h
  • lib/WebServer/src/uri/UriRegex.h
  • src/network/CrossPointWebServer.cpp
  • src/network/CrossPointWebServer.h
  • src/network/WebDAVHandler.cpp
  • src/network/WebDAVHandler.h
🧰 Additional context used
🧬 Code graph analysis (9)
lib/WebServer/src/detail/RequestHandler.h (5)
lib/WebServer/src/detail/RequestHandlersImpl.h (5)
  • canHandle (26-31)
  • canHandle (77-85)
  • canUpload (33-38)
  • handle (40-47)
  • handle (87-125)
lib/WebServer/src/uri/UriBraces.h (1)
  • canHandle (28-63)
lib/WebServer/src/uri/UriGlob.h (1)
  • canHandle (17-19)
lib/WebServer/src/uri/UriRegex.h (1)
  • canHandle (25-41)
lib/WebServer/src/WebServer.cpp (5)
  • WebServer (42-64)
  • WebServer (66-88)
  • WebServer (90-100)
  • pathArg (543-547)
  • pathArg (543-543)
lib/WebServer/src/uri/UriRegex.h (4)
lib/WebServer/src/Uri.h (1)
  • Uri (7-27)
lib/WebServer/src/WebServer.h (1)
  • uri (96-96)
lib/WebServer/src/uri/UriGlob.h (2)
  • clone (13-15)
  • canHandle (17-19)
lib/WebServer/src/detail/RequestHandlersImpl.h (2)
  • canHandle (26-31)
  • canHandle (77-85)
src/network/WebDAVHandler.h (2)
src/network/WebDAVHandler.cpp (43)
  • WebDAVHandler (21-21)
  • handleRequest (23-63)
  • handleRequest (23-23)
  • handleOptions (67-75)
  • handleOptions (67-67)
  • handlePropfind (79-167)
  • handlePropfind (79-79)
  • handleGet (204-237)
  • handleGet (204-204)
  • handleHead (241-271)
  • handleHead (241-241)
  • handlePut (275-337)
  • handlePut (275-275)
  • handleDelete (341-390)
  • handleDelete (341-341)
  • handleMkcol (394-430)
  • handleMkcol (394-394)
  • handleMove (434-496)
  • handleMove (434-434)
  • handleCopy (500-586)
  • handleCopy (500-500)
  • handleLock (590-611)
  • handleLock (590-590)
  • handleUnlock (613-616)
  • handleUnlock (613-613)
  • getRequestPath (620-637)
  • getRequestPath (620-620)
  • getDestinationPath (639-668)
  • getDestinationPath (639-639)
  • urlEncodePath (670-695)
  • urlEncodePath (670-670)
  • isProtectedPath (697-713)
  • isProtectedPath (697-697)
  • getDepth (715-721)
  • getDepth (715-715)
  • getOverwrite (723-727)
  • getOverwrite (723-723)
  • clearEpubCacheIfNeeded (729-734)
  • clearEpubCacheIfNeeded (729-729)
  • sendPropEntry (169-200)
  • sendPropEntry (169-170)
  • getMimeType (736-754)
  • getMimeType (736-736)
lib/Epub/Epub/hyphenation/LanguageRegistry.h (1)
  • const (14-19)
lib/WebServer/src/WebServer.cpp (4)
lib/WebServer/src/WebServer.h (4)
  • WebServer (70-213)
  • uri (96-96)
  • method (97-97)
  • WiFiClient (98-98)
lib/WebServer/src/detail/RequestHandler.h (2)
  • next (15-15)
  • next (16-16)
lib/WebServer/src/detail/mimetable.h (1)
  • mime (5-44)
lib/WebServer/src/Parsing.cpp (2)
  • _parseRequest (76-252)
  • _parseRequest (76-76)
src/network/CrossPointWebServer.h (2)
src/network/WebDAVHandler.cpp (1)
  • WebDAVHandler (21-21)
src/network/WebDAVHandler.h (1)
  • WebDAVHandler (5-38)
lib/WebServer/src/Uri.h (5)
lib/WebServer/src/WebServer.h (1)
  • uri (96-96)
lib/WebServer/src/uri/UriBraces.h (3)
  • clone (12-14)
  • initPathArgs (16-26)
  • canHandle (28-63)
lib/WebServer/src/uri/UriGlob.h (2)
  • clone (13-15)
  • canHandle (17-19)
lib/WebServer/src/uri/UriRegex.h (3)
  • clone (13-15)
  • initPathArgs (17-42)
  • canHandle (25-41)
lib/WebServer/src/detail/RequestHandlersImpl.h (2)
  • canHandle (26-31)
  • canHandle (77-85)
lib/WebServer/src/Parsing.cpp (3)
lib/WebServer/src/WebServer.h (1)
  • method (97-97)
lib/WebServer/src/WebServer.cpp (8)
  • arg (549-559)
  • arg (549-549)
  • arg (561-565)
  • arg (561-561)
  • argName (567-571)
  • argName (567-567)
  • hasArg (577-587)
  • hasArg (577-577)
lib/WebServer/src/detail/mimetable.h (1)
  • mime (5-44)
lib/WebServer/src/detail/RequestHandlersImpl.h (6)
lib/WebServer/src/detail/mimetable.h (1)
  • mime (5-44)
lib/WebServer/src/detail/RequestHandler.h (1)
  • RequestHandler (7-28)
lib/WebServer/src/WebServer.h (4)
  • WebServer (70-213)
  • uri (96-96)
  • method (97-97)
  • fs (66-68)
lib/WebServer/src/Uri.h (1)
  • Uri (7-27)
lib/WebServer/src/uri/UriGlob.h (1)
  • canHandle (17-19)
lib/WebServer/src/uri/UriRegex.h (1)
  • canHandle (25-41)
lib/WebServer/src/WebServer.h (2)
lib/WebServer/src/WebServer.cpp (47)
  • WebServer (42-64)
  • WebServer (66-88)
  • WebServer (90-100)
  • begin (102-106)
  • begin (102-102)
  • begin (108-112)
  • begin (108-108)
  • close (354-359)
  • close (354-354)
  • on (255-257)
  • on (255-255)
  • on (259-261)
  • on (259-259)
  • on (263-265)
  • on (263-263)
  • onNotFound (641-643)
  • onNotFound (641-641)
  • arg (549-559)
  • arg (549-549)
  • arg (561-565)
  • arg (561-561)
  • collectHeaders (598-607)
  • collectHeaders (598-598)
  • header (590-596)
  • header (590-590)
  • header (609-613)
  • header (609-609)
  • headerName (615-619)
  • headerName (615-615)
  • send (429-441)
  • send (429-429)
  • send (443-445)
  • send (443-443)
  • send (447-449)
  • send (447-447)
  • send (451-458)
  • send (451-451)
  • sendHeader (365-377)
  • sendHeader (365-365)
  • _responseCodeToString (678-722)
  • _responseCodeToString (678-678)
  • _prepareHeader (395-427)
  • _prepareHeader (395-395)
  • _getRandomHexString (229-236)
  • _getRandomHexString (229-229)
  • _extractParam (114-119)
  • _extractParam (114-114)
lib/WebServer/src/Parsing.cpp (4)
  • urlDecode (563-592)
  • urlDecode (563-563)
  • _collectHeader (254-262)
  • _collectHeader (254-254)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (23)
lib/WebServer/library.json (1)

1-6: LGTM — minimal, valid PlatformIO manifest.

lib/WebServer/src/HTTP_Method.h (1)

1-9: LGTM — clean method alias and sentinel.

lib/WebServer/src/Uri.h (1)

1-27: LGTM — solid base URI abstraction.

lib/WebServer/src/Parsing.cpp (9)

44-74: LGTM — timeout-based buffered read is clear and safe on realloc failure.


177-211: Good WebDAV streaming guard for PUT bodies.


254-262: LGTM — header collection is straightforward.


264-311: LGTM — argument parsing is consistent with URL-decoding expectations.


313-321: LGTM — upload buffering and callback cadence look correct.


323-362: LGTM — retry loop handles transient read() failures sensibly.


364-561: LGTM — multipart parsing and upload lifecycle logic is intact.


563-592: LGTM — URL decode utility is clear and correct.


594-599: LGTM — upload abort path cleanly notifies handler.

src/network/CrossPointWebServer.h (1)

12-76: LGTM — WebDAV handler ownership is clear and RAII-safe.

src/network/CrossPointWebServer.cpp (3)

222-222: Good cleanup of WebDAV handler during stop.


314-316: Nice: WebDAV gets first right-of-refusal before 404.


162-166: No action needed—collected headers are sufficient.

WebDAVHandler only reads three headers from incoming requests: Destination (line 640), Depth (line 716), and Overwrite (line 724). All three are in the collected list. WebDAVHandler does not access Content-Length, Content-Type, or any other headers beyond those being collected, so the current header collection is complete and correct.

lib/WebServer/src/detail/mimetable.cpp (1)

1-33: LGTM — MIME table is comprehensive and ordered as expected.

lib/WebServer/src/detail/mimetable.h (1)

1-43: LGTM — header matches the MIME table definition.

src/network/WebDAVHandler.h (1)

5-37: API surface looks consistent.
Clean separation between dispatch and per-method handlers; utilities are logically grouped.

lib/WebServer/src/detail/RequestHandler.h (1)

7-29: Base handler interface looks solid.
Clean chaining API and path-arg accessors.

lib/WebServer/src/uri/UriRegex.h (1)

4-40: UriRegex is not used in this project; std::regex concern is moot.

The CrossPointWebServer implementation uses simple string-based routing (e.g., server->on("/", HTTP_GET, ...)), not the Uri pattern-matching classes. While UriRegex and its std::regex dependency exist in the bundled WebServer library, they are never instantiated or invoked by the application code. No action needed.

Likely an incorrect or invalid review comment.

lib/WebServer/src/WebServer.h (2)

33-62: LGTM for enums, constants, and HTTPUpload layout.

The sizing macros and upload struct look consistent with the expected single-client, embedded constraints.


73-95: Routing/handler API surface looks solid.

The overload set for on(...), plus onNotFound/onFileUpload, provides clean registration points for WebDAV and static routes.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/WebServer/src/detail/RequestHandlersImpl.h`:
- Around line 77-83: canHandle currently allows prefix collisions (e.g., "/foo"
matching "/foobar") because it only checks startsWith(_uri); change the
predicate so when _isFile is true require exact equality (requestUri == _uri),
and when not _isFile require either exact match or that requestUri starts with
_uri followed by a path separator (e.g., requestUri == _uri ||
requestUri.startsWith(_uri + "/")). Update the logic in canHandle to use
_isFile, _uri, requestUri and HTTP_GET accordingly so static routes only match
the exact path or a proper subpath.

In `@lib/WebServer/src/Parsing.cpp`:
- Around line 220-233: The header value parsing drops the first character when
headers are in "Header:Value" form because it uses substring(headerDiv + 2);
change the logic in the headers loop to extract headerValue starting at
headerDiv + 1 (i.e., substring(headerDiv + 1)) and then trim a single leading
space if present (so both "Header:Value" and "Header: Value" are handled).
Update the variables/locations referenced (req, headerDiv, headerName,
headerValue) and continue to call _collectHeader(headerName.c_str(),
headerValue.c_str()) with the corrected value.

In `@lib/WebServer/src/uri/UriBraces.h`:
- Around line 16-26: The placeholder counting in initPathArgs incorrectly
ignores matches at index 0 because it checks start > 0; update the loop in
UriBraces::initPathArgs to treat any non-negative index as a valid match (use
start >= 0 or check for start != -1), increment numParams when start >= 0, then
advance start by 2 and continue while start >= 0; this will ensure
pathArgs.resize(numParams) allocates slots for placeholders at the beginning and
prevents canHandle from writing out of bounds.

In `@lib/WebServer/src/uri/UriGlob.h`:
- Around line 4-19: The current UriGlob class unconditionally uses fnmatch
(included via fnmatch.h) which breaks builds on platforms without fnmatch; wrap
the include and the UriGlob class implementation with a compile-time guard
(e.g., `#ifdef` HAVE_FNMATCH / `#endif`) or introduce a portability macro (e.g.,
USE_FNMATCH) and provide a fallback: if fnmatch is unavailable, either implement
a lightweight glob matcher used by UriGlob::canHandle(const String &requestUri,
std::vector<String> &pathArgs) or implement UriGlob as a thin wrapper that
always returns false from canHandle and documents the limitation; keep
UriGlob::clone() consistent with whichever implementation is chosen and ensure
the guard symbol is documented in the build system so toolchains without fnmatch
can compile.

In `@lib/WebServer/src/WebServer.cpp`:
- Around line 147-154: The code uses a signed 8-bit char for the length
(toencodeLen) causing potential overflow and wrong allocations; change to an
unsigned size type (e.g., size_t or int) when computing
strlen(username)+strlen(password)+1 and use that type for allocations and when
calling base64_encode_expected_len, updating any related variables (toencodeLen,
toencode, encoded) in WebServer.cpp so buffer sizes are computed correctly and
safely; ensure null-checks and allocations use the new size type consistently.
- Around line 197-210: The current digest hash only handles GET/POST/PUT/DELETE
leading to WebDAV methods failing; update the logic around _currentMethod that
builds _H2 so it uses the actual HTTP method string instead of the hardcoded
if/else chain—e.g., derive a methodName from _currentMethod (or use an existing
helper like httpMethodToString/_request->method() if available) that returns
"GET", "POST", "PROPFIND", "MKCOL", "MOVE", etc., then compute _H2 =
md5str(String(methodName) + ':' + _uri) (fallback to "GET" only if methodName is
unknown); reference md5str, _currentMethod, _uri and _H2 when making the change.

In `@lib/WebServer/src/WebServer.h`:
- Around line 2-4: The file header in WebServer.h incorrectly states the server
"Supports only one simultaneous client, knows how to handle GET and POST";
update that top-of-file comment to accurately describe current capabilities of
the WebServer class—mention its concurrency support (single/multi-client as
implemented), and list the actual HTTP methods supported (as implemented by
methods like handleClient() and handleRequest()), and any important behavior
(e.g., request parsing, routing, or payload handling) so integrators aren’t
misled; keep the wording brief and factual to match the current implementation.
- Around line 96-100: The upload() accessor dereferences the std::unique_ptr
_currentUpload unsafely; update WebServer::upload() to guard against null by
returning a safe fallback (for example a reference to a static empty HTTPUpload
instance) or change the API to return a pointer/optional so callers must handle
absence, and ensure callers that expect an active upload still function; refer
to symbols upload(), _currentUpload, HTTPUpload and update WebServer::upload()
implementation (or add clear documentation on the precondition) so dereferencing
a null unique_ptr is never possible.

In `@src/network/WebDAVHandler.cpp`:
- Around line 697-712: isProtectedPath currently only examines the final path
segment so nested hidden directories like "/.hidden/file" bypass protection;
update isProtectedPath to iterate over every path segment (splitting on '/' or
scanning between '/' chars) and for each non-empty segment check if
segment.startsWith(".") or equals any entry in HIDDEN_ITEMS (using HIDDEN_ITEMS
and HIDDEN_ITEMS_COUNT) and return true if so, otherwise return false; keep
existing behavior for empty paths.
- Around line 319-336: The PUT handler currently uses client.readBytes(...) and
file.write(...) without verifying they processed the full requested amount, so
partial reads or short writes produce a truncated file while still returning
201/204; update the upload loop in WebDAVHandler::PUT to check that bytesRead ==
toRead (and break with an error on timeout/0), check the return value of
file.write (or bytesWritten) equals bytesRead, and on any mismatch or failure
close and remove the partial file (or rollback) and send an error response
(e.g., 408/500) instead of 201/204; reference the loop using client.readBytes,
file.write, remaining/contentLength, file.close, and _server->send to locate and
implement these checks and proper cleanup.

---

Nitpick comments:
In `@lib/WebServer/src/WebServer.h`:
- Line 216: The closing `#endif` comment in WebServer.h currently reads
"//ESP8266WEBSERVER_H" but doesn't match the actual header guard macro; open
WebServer.h, locate the top guard macro (the `#ifndef/`#define symbol used for the
include guard) and update the trailing comment on the final `#endif` to exactly
match that macro name (e.g., change the comment to the same identifier found at
the top) so the end-comment aligns with the real guard (reference the macro
symbol at the top of WebServer.h and the existing trailing comment
"ESP8266WEBSERVER_H").

Comment on lines +77 to +83
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;

if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid prefix collisions in static routing.
Line 81 allows /foo to match /foobar, which can resolve unintended files. Add a boundary check so only /foo or /foo/... matches.

🛠️ Proposed fix
     bool canHandle(HTTPMethod requestMethod, String requestUri) override  {
         if (requestMethod != HTTP_GET)
             return false;
 
-        if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
-            return false;
+        if (_isFile) {
+            if (requestUri != _uri)
+                return false;
+        } else {
+            String base = _uri;
+            if (!base.endsWith("/"))
+                base += "/";
+            if (!(requestUri == _uri || requestUri.startsWith(base)))
+                return false;
+        }
 
         return true;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))
return false;
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
if (requestMethod != HTTP_GET)
return false;
if (_isFile) {
if (requestUri != _uri)
return false;
} else {
String base = _uri;
if (!base.endsWith("/"))
base += "/";
if (!(requestUri == _uri || requestUri.startsWith(base)))
return false;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/detail/RequestHandlersImpl.h` around lines 77 - 83,
canHandle currently allows prefix collisions (e.g., "/foo" matching "/foobar")
because it only checks startsWith(_uri); change the predicate so when _isFile is
true require exact equality (requestUri == _uri), and when not _isFile require
either exact match or that requestUri starts with _uri followed by a path
separator (e.g., requestUri == _uri || requestUri.startsWith(_uri + "/")).
Update the logic in canHandle to use _isFile, _uri, requestUri and HTTP_GET
accordingly so static routes only match the exact path or a proper subpath.

Comment on lines +220 to +233
String headerName;
String headerValue;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(),headerValue.c_str());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix header value parsing when there's no space after “:”.

HTTP allows Header:Value (no space). Using substring(headerDiv + 2) will drop the first character in that case. Align with the POST/PUT branch by trimming after +1.

🛠️ Suggested fix
-      headerValue = req.substring(headerDiv + 2);
-      _collectHeader(headerName.c_str(),headerValue.c_str());
+      headerValue = req.substring(headerDiv + 1);
+      headerValue.trim();
+      _collectHeader(headerName.c_str(), headerValue.c_str());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String headerName;
String headerValue;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 2);
_collectHeader(headerName.c_str(),headerValue.c_str());
String headerName;
String headerValue;
//parse headers
while(1){
req = client.readStringUntil('\r');
client.readStringUntil('\n');
if (req == "") break;//no moar headers
int headerDiv = req.indexOf(':');
if (headerDiv == -1){
break;
}
headerName = req.substring(0, headerDiv);
headerValue = req.substring(headerDiv + 1);
headerValue.trim();
_collectHeader(headerName.c_str(), headerValue.c_str());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/Parsing.cpp` around lines 220 - 233, The header value
parsing drops the first character when headers are in "Header:Value" form
because it uses substring(headerDiv + 2); change the logic in the headers loop
to extract headerValue starting at headerDiv + 1 (i.e., substring(headerDiv +
1)) and then trim a single leading space if present (so both "Header:Value" and
"Header: Value" are handled). Update the variables/locations referenced (req,
headerDiv, headerName, headerValue) and continue to call
_collectHeader(headerName.c_str(), headerValue.c_str()) with the corrected
value.

Comment on lines +16 to +26
void initPathArgs(std::vector<String> &pathArgs) override final {
int numParams = 0, start = 0;
do {
start = _uri.indexOf("{}", start);
if (start > 0) {
numParams++;
start += 2;
}
} while (start > 0);
pathArgs.resize(numParams);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix placeholder counting at index 0 (prevents OOB).
Line 20 only counts matches where start > 0, so patterns beginning with {} allocate zero args and canHandle writes out of bounds.

🛠️ Proposed fix
-        void initPathArgs(std::vector<String> &pathArgs) override final {
-            int numParams = 0, start = 0;
-            do {
-                start = _uri.indexOf("{}", start);
-                if (start > 0) {
-                    numParams++;
-                    start += 2;
-                }
-            } while (start > 0);
-            pathArgs.resize(numParams);
-        }
+        void initPathArgs(std::vector<String> &pathArgs) override final {
+            int numParams = 0;
+            int start = 0;
+            while ((start = _uri.indexOf("{}", start)) >= 0) {
+                numParams++;
+                start += 2;
+            }
+            pathArgs.resize(numParams);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/uri/UriBraces.h` around lines 16 - 26, The placeholder
counting in initPathArgs incorrectly ignores matches at index 0 because it
checks start > 0; update the loop in UriBraces::initPathArgs to treat any
non-negative index as a valid match (use start >= 0 or check for start != -1),
increment numParams when start >= 0, then advance start by 2 and continue while
start >= 0; this will ensure pathArgs.resize(numParams) allocates slots for
placeholders at the beginning and prevents canHandle from writing out of bounds.

Comment on lines +4 to +19
#include "Uri.h"
#include <fnmatch.h>

class UriGlob : public Uri {

public:
explicit UriGlob(const char *uri) : Uri(uri) {};
explicit UriGlob(const String &uri) : Uri(uri) {};

Uri* clone() const override final {
return new UriGlob(_uri);
};

bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check fnmatch usage and any compatibility guards/fallbacks
rg -n "fnmatch|UriGlob" lib/WebServer -g '*.h' -g '*.cpp'

Repository: crosspoint-reader/crosspoint-reader

Length of output: 560


🏁 Script executed:

#!/bin/bash
# Check for any conditional compilation or platform-specific code
rg -n "#ifdef|#if|fnmatch|ESP32" lib/WebServer --context 2 -g '*.h' -g '*.cpp'

Repository: crosspoint-reader/crosspoint-reader

Length of output: 2695


🏁 Script executed:

#!/bin/bash
# Look for include guards or platform detection
rg -n "include.*fnmatch|HAVE_FNMATCH|_GNU_SOURCE" -g '*.h' -g '*.cpp'

Repository: crosspoint-reader/crosspoint-reader

Length of output: 133


Add compatibility guards for fnmatch or provide a fallback implementation.

fnmatch.h is included unconditionally, but this POSIX function is not available on all embedded platforms (notably some ESP32 toolchains). The code will fail to compile if fnmatch is unavailable. Guard the include and class definition with #ifdef or provide an alternative glob matcher.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/uri/UriGlob.h` around lines 4 - 19, The current UriGlob
class unconditionally uses fnmatch (included via fnmatch.h) which breaks builds
on platforms without fnmatch; wrap the include and the UriGlob class
implementation with a compile-time guard (e.g., `#ifdef` HAVE_FNMATCH / `#endif`) or
introduce a portability macro (e.g., USE_FNMATCH) and provide a fallback: if
fnmatch is unavailable, either implement a lightweight glob matcher used by
UriGlob::canHandle(const String &requestUri, std::vector<String> &pathArgs) or
implement UriGlob as a thin wrapper that always returns false from canHandle and
documents the limitation; keep UriGlob::clone() consistent with whichever
implementation is chosen and ensure the guard symbol is documented in the build
system so toolchains without fnmatch can compile.

Comment on lines +147 to +154
char toencodeLen = strlen(username)+strlen(password)+1;
char *toencode = new char[toencodeLen + 1];
if(toencode == NULL){
authReq = "";
return false;
}
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
if(encoded == NULL){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Basic auth length uses char, risking overflow.
Line 147 stores the length in a char, which can overflow for longer credentials and under-allocate buffers. Use size_t or int.

🛠️ Proposed fix
-      char toencodeLen = strlen(username)+strlen(password)+1;
+      size_t toencodeLen = strlen(username) + strlen(password) + 1;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/WebServer.cpp` around lines 147 - 154, The code uses a
signed 8-bit char for the length (toencodeLen) causing potential overflow and
wrong allocations; change to an unsigned size type (e.g., size_t or int) when
computing strlen(username)+strlen(password)+1 and use that type for allocations
and when calling base64_encode_expected_len, updating any related variables
(toencodeLen, toencode, encoded) in WebServer.cpp so buffer sizes are computed
correctly and safely; ensure null-checks and allocations use the new size type
consistently.

Comment on lines +197 to +210
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
log_v("Hash of user:realm:pass=%s", _H1.c_str());
String _H2 = "";
if(_currentMethod == HTTP_GET){
_H2 = md5str(String(F("GET:")) + _uri);
}else if(_currentMethod == HTTP_POST){
_H2 = md5str(String(F("POST:")) + _uri);
}else if(_currentMethod == HTTP_PUT){
_H2 = md5str(String(F("PUT:")) + _uri);
}else if(_currentMethod == HTTP_DELETE){
_H2 = md5str(String(F("DELETE:")) + _uri);
}else{
_H2 = md5str(String(F("GET:")) + _uri);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Digest auth misses WebDAV methods.
Line 200 only hashes GET/POST/PUT/DELETE, so WebDAV methods (PROPFIND, MKCOL, MOVE, etc.) will always fail Digest authentication.

🛠️ Proposed fix
-      String _H2 = "";
-      if(_currentMethod == HTTP_GET){
-          _H2 = md5str(String(F("GET:")) + _uri);
-      }else if(_currentMethod == HTTP_POST){
-          _H2 = md5str(String(F("POST:")) + _uri);
-      }else if(_currentMethod == HTTP_PUT){
-          _H2 = md5str(String(F("PUT:")) + _uri);
-      }else if(_currentMethod == HTTP_DELETE){
-          _H2 = md5str(String(F("DELETE:")) + _uri);
-      }else{
-          _H2 = md5str(String(F("GET:")) + _uri);
-      }
+      String methodName;
+      switch (_currentMethod) {
+        case HTTP_GET:      methodName = F("GET"); break;
+        case HTTP_POST:     methodName = F("POST"); break;
+        case HTTP_PUT:      methodName = F("PUT"); break;
+        case HTTP_DELETE:   methodName = F("DELETE"); break;
+        case HTTP_HEAD:     methodName = F("HEAD"); break;
+        case HTTP_OPTIONS:  methodName = F("OPTIONS"); break;
+        case HTTP_PROPFIND: methodName = F("PROPFIND"); break;
+        case HTTP_MKCOL:    methodName = F("MKCOL"); break;
+        case HTTP_MOVE:     methodName = F("MOVE"); break;
+        case HTTP_COPY:     methodName = F("COPY"); break;
+        case HTTP_LOCK:     methodName = F("LOCK"); break;
+        case HTTP_UNLOCK:   methodName = F("UNLOCK"); break;
+        default:            methodName = F("GET"); break;
+      }
+      String _H2 = md5str(methodName + ':' + _uri);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
log_v("Hash of user:realm:pass=%s", _H1.c_str());
String _H2 = "";
if(_currentMethod == HTTP_GET){
_H2 = md5str(String(F("GET:")) + _uri);
}else if(_currentMethod == HTTP_POST){
_H2 = md5str(String(F("POST:")) + _uri);
}else if(_currentMethod == HTTP_PUT){
_H2 = md5str(String(F("PUT:")) + _uri);
}else if(_currentMethod == HTTP_DELETE){
_H2 = md5str(String(F("DELETE:")) + _uri);
}else{
_H2 = md5str(String(F("GET:")) + _uri);
}
String methodName;
switch (_currentMethod) {
case HTTP_GET: methodName = F("GET"); break;
case HTTP_POST: methodName = F("POST"); break;
case HTTP_PUT: methodName = F("PUT"); break;
case HTTP_DELETE: methodName = F("DELETE"); break;
case HTTP_HEAD: methodName = F("HEAD"); break;
case HTTP_OPTIONS: methodName = F("OPTIONS"); break;
case HTTP_PROPFIND: methodName = F("PROPFIND"); break;
case HTTP_MKCOL: methodName = F("MKCOL"); break;
case HTTP_MOVE: methodName = F("MOVE"); break;
case HTTP_COPY: methodName = F("COPY"); break;
case HTTP_LOCK: methodName = F("LOCK"); break;
case HTTP_UNLOCK: methodName = F("UNLOCK"); break;
default: methodName = F("GET"); break;
}
String _H2 = md5str(methodName + ':' + _uri);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/WebServer.cpp` around lines 197 - 210, The current digest
hash only handles GET/POST/PUT/DELETE leading to WebDAV methods failing; update
the logic around _currentMethod that builds _H2 so it uses the actual HTTP
method string instead of the hardcoded if/else chain—e.g., derive a methodName
from _currentMethod (or use an existing helper like
httpMethodToString/_request->method() if available) that returns "GET", "POST",
"PROPFIND", "MKCOL", "MOVE", etc., then compute _H2 = md5str(String(methodName)
+ ':' + _uri) (fallback to "GET" only if methodName is unknown); reference
md5str, _currentMethod, _uri and _H2 when making the change.

Comment on lines +2 to +4
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update the header comment to reflect current method support.

The file header says the server only handles GET/POST, which is now outdated and potentially misleading for integrators.

✍️ Suggested wording
-  Supports only one simultaneous client, knows how to handle GET and POST.
+  Supports only one simultaneous client and handles multiple HTTP methods (e.g., GET/POST/PUT/DELETE/OPTIONS).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
WebServer.h - Dead simple web-server.
Supports only one simultaneous client, knows how to handle GET and POST.
WebServer.h - Dead simple web-server.
Supports only one simultaneous client and handles multiple HTTP methods (e.g., GET/POST/PUT/DELETE/OPTIONS).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/WebServer.h` around lines 2 - 4, The file header in
WebServer.h incorrectly states the server "Supports only one simultaneous
client, knows how to handle GET and POST"; update that top-of-file comment to
accurately describe current capabilities of the WebServer class—mention its
concurrency support (single/multi-client as implemented), and list the actual
HTTP methods supported (as implemented by methods like handleClient() and
handleRequest()), and any important behavior (e.g., request parsing, routing, or
payload handling) so integrators aren’t misled; keep the wording brief and
factual to match the current implementation.

Comment on lines +96 to +100
String uri() { return _currentUri; }
HTTPMethod method() { return _currentMethod; }
virtual WiFiClient client() { return _currentClient; }
HTTPUpload& upload() { return *_currentUpload; }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for _currentUpload in WebServer.cpp to understand initialization and usage
rg -n '_currentUpload' lib/WebServer/src/WebServer.cpp | head -50

Repository: crosspoint-reader/crosspoint-reader

Length of output: 110


🏁 Script executed:

#!/bin/bash
# Search for upload() method calls in the entire codebase
rg -n '\.upload\s*\(' --type cpp --type h

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Find the constructor of WebServer to see initialization
rg -n 'WebServer::WebServer|WebServer(' lib/WebServer/src/WebServer.cpp | head -20

Repository: crosspoint-reader/crosspoint-reader

Length of output: 191


🏁 Script executed:

#!/bin/bash
# Get more context around _currentUpload usage
rg -n -B5 -A5 '_currentUpload' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 289


🏁 Script executed:

#!/bin/bash
# Check the header file for _currentUpload declaration
rg -n '_currentUpload' lib/WebServer/src/WebServer.h

Repository: crosspoint-reader/crosspoint-reader

Length of output: 182


🏁 Script executed:

#!/bin/bash
# Search for upload() method calls - simpler regex
rg -n '\.upload\(\)' --type cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Find the WebServer constructor(s) to see initialization
rg -n 'WebServer::' lib/WebServer/src/WebServer.cpp | head -20

Repository: crosspoint-reader/crosspoint-reader

Length of output: 1348


🏁 Script executed:

#!/bin/bash
# Search in all cpp/h files for upload() usage
find lib -name '*.cpp' -o -name '*.h' | xargs grep -n 'upload()'

Repository: crosspoint-reader/crosspoint-reader

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Look for constructor implementation more broadly
sed -n '1,100p' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 2906


🏁 Script executed:

#!/bin/bash
# Check complete constructor initialization lists
sed -n '42,88p' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 1149


🏁 Script executed:

#!/bin/bash
# Search for where _currentUpload is assigned a value
rg -n '_currentUpload\s*=' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Look for HTTPUpload allocation/creation
rg -n 'new HTTPUpload|make_unique<HTTPUpload>' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Broader search for HTTPUpload creation/allocation
rg -n 'HTTPUpload' lib/WebServer/src/WebServer.cpp | head -30

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Look at handleClient implementation since that's where upload might be used
sed -n '286,400p' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 3037


🏁 Script executed:

#!/bin/bash
# Check if there's any guard before dereferencing _currentUpload
rg -n -B10 '\*_currentUpload' lib/WebServer/src/WebServer.cpp

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


🏁 Script executed:

#!/bin/bash
# Check the upload() method context and visibility
sed -n '90,110p' lib/WebServer/src/WebServer.h

Repository: crosspoint-reader/crosspoint-reader

Length of output: 1422


🏁 Script executed:

#!/bin/bash
# Search entire repo for HTTPUpload to see if it's allocated anywhere
find . -type f \( -name '*.cpp' -o -name '*.h' \) | xargs grep -l 'HTTPUpload' | head -10

Repository: crosspoint-reader/crosspoint-reader

Length of output: 273


🏁 Script executed:

#!/bin/bash
# Look for RequestHandler implementations that might allocate HTTPUpload
rg -n 'HTTPUpload' lib/WebServer/src/detail/RequestHandlersImpl.h | head -20

Repository: crosspoint-reader/crosspoint-reader

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Check Parsing.cpp for _currentUpload initialization
rg -n '_currentUpload' lib/WebServer/src/Parsing.cpp -B5 -A5

Repository: crosspoint-reader/crosspoint-reader

Length of output: 3127


🏁 Script executed:

#!/bin/bash
# Search for _handleRequest implementation
rg -n '_handleRequest' lib/WebServer/src/WebServer.cpp | head -5

Repository: crosspoint-reader/crosspoint-reader

Length of output: 149


🏁 Script executed:

#!/bin/bash
# Look for where upload handlers are called
rg -n '\.upload\(' lib/WebServer/src/ -B3 -A3

Repository: crosspoint-reader/crosspoint-reader

Length of output: 61


Add null safety check to upload() accessor or document the precondition.

upload() dereferences _currentUpload unconditionally, but _currentUpload is a std::unique_ptr that starts as null and is only allocated during multipart file upload parsing (line 439 in Parsing.cpp). After request completion, it's reset to null (line 346 in WebServer.cpp). Since upload() is public, callers outside the upload handler context will crash with a null pointer dereference. Either add a null check and return a safe fallback, or clearly document that upload() must only be called from within an upload handler.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/WebServer/src/WebServer.h` around lines 96 - 100, The upload() accessor
dereferences the std::unique_ptr _currentUpload unsafely; update
WebServer::upload() to guard against null by returning a safe fallback (for
example a reference to a static empty HTTPUpload instance) or change the API to
return a pointer/optional so callers must handle absence, and ensure callers
that expect an active upload still function; refer to symbols upload(),
_currentUpload, HTTPUpload and update WebServer::upload() implementation (or add
clear documentation on the precondition) so dereferencing a null unique_ptr is
never possible.

@el
Copy link
Contributor

el commented Feb 20, 2026

I like the idea, but the implementation seems pretty heavy. Maybe it could be easier to add FTP support instead?

@dexif
Copy link
Contributor Author

dexif commented Feb 20, 2026

I like the idea, but the implementation seems pretty heavy. Maybe it could be easier to add FTP support instead?

I made a pull request to the library (once they approve the fix, we can remove the library files). There aren't really many changes, as I tried to avoid pulling in third-party libraries. However, I had to patch the existing one to be able to load files larger than 100 KB.

And I tested it and everything works great. It seems to me that ftp will be significantly more expensive in terms of resources and code.

// Collect WebDAV headers and create handler
const char* davHeaders[] = {"Depth", "Destination", "Overwrite", "If", "Lock-Token", "Timeout"};
server->collectHeaders(davHeaders, 6);
davHandler.reset(new WebDAVHandler(server.get()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This automatically starts the WebDav server when "File Transfer" is used afaics. Is that something we want? I assumed we would have a 4th option in "File Transfer" menu as WebDav Server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Essentially, we're already running the web server. In this case, I don't see the point in creating a separate section for enabling webdav, given that processing is happening there as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Webserver is already pretty slow and resource intensive, wouldnt this put more pressure on it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can’t imagine what could create load here. Essentially, it’s just a few additional request handlers.

@Jason2866
Copy link
Contributor

I like the idea, but the implementation seems pretty heavy. Maybe it could be easier to add FTP support instead?

I made a pull request to the library (once they approve the fix, we can remove the library files). There aren't really many changes, as I tried to avoid pulling in third-party libraries. However, I had to patch the existing one to be able to load files larger than 100 KB.

And I tested it and everything works great. It seems to me that ftp will be significantly more expensive in terms of resources and code.

Your current platformio.ini does not use actual Arduino core. If your PR in espressif32 Arduino core is accepted you have to change to switch from your currently used Arduino core 2.0.17 to actual 3.3.x
Core 2.0.17 is outdated and out of support.

@dexif
Copy link
Contributor Author

dexif commented Feb 20, 2026

Core 2.0.17 is outdated and out of support.

If the pull request to the framework is accepted, I was planning to work on the update.

  - isProtectedPath: check every path segment, not just the last one,
    to block access to e.g. /.hidden/file or /System Volume Information/foo
  - handlePut: verify readBytes/write results and remove partial file
    on failure instead of returning 201/204 for truncated uploads
@dexif
Copy link
Contributor Author

dexif commented Feb 20, 2026

I'll open a new pull request. It will include library updates and a webdav implementation. Just to make it a clean pull request.

@dexif dexif closed this Feb 20, 2026
daveallie added a commit that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR #1030 (was closed because the implementation was based on an
old version of the libraries)
Issue #439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 22, 2026
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](open-x4-epaper/community-sdk#21)

First PR crosspoint-reader#1030 (was closed because the implementation was based on an
old version of the libraries)
Issue crosspoint-reader#439

---------

Co-authored-by: Dave Allie <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants