Skip to content

fix: WebDAV restore crash - _Map<String,dynamic> is not a subtype of ResponseBody? - #2360

Open
bighamx wants to merge 1 commit into
chen08209:mainfrom
bighamx:fix/webdav-restore-responsebody
Open

fix: WebDAV restore crash - _Map<String,dynamic> is not a subtype of ResponseBody?#2360
bighamx wants to merge 1 commit into
chen08209:mainfrom
bighamx:fix/webdav-restore-responsebody

Conversation

@bighamx

@bighamx bighamx commented Aug 25, 2026

Copy link
Copy Markdown

What this fixes

WebDAV restore crashes on all platforms (Windows / Android) right after a successful backup:

type '_Map<String, dynamic>' is not a subtype of type 'ResponseBody?' of 'value'
  #0 Response.data= (package:dio/src/response.dart:30)
  #1 WdDio.wdReadWithStream (package:webdav_client/src/webdav_dio.dart:318)

Root cause

This is not a bug in FlClash's business code — it is a type-incompatibility between the webdav_client dependency (v1.2.2) and Dio 5.x.

Restore goes through webdav_client's read2File()wdReadWithStream(), which does:

Response<ResponseBody> resp;
resp = await this.req(   // ← no type argument, so T falls back to `dynamic`
  self, 'GET', path,
  optionsHandler: (o) => o.responseType = ResponseType.stream,
  ...
);

req<T> is called without an explicit type, so T resolves to dynamic and propagates into Dio's requestUri<T>. In today's Dio 5.x, fetch<T> then resets RequestOptions.responseType to ResponseType.json — so the response body is decoded into a Map<String, dynamic>, while wdReadWithStream declares the variable as Response<ResponseBody>. Dio 5.x's assureResponse<T> finally performs the covariant cast response.data as T? (T = ResponseBody), which throws the reported error.

Backup succeeds because it uses PUT (wdWriteWithStream), whose resp is a non-generic Response and never hits the ResponseBody covariance assertion. So all three clients share the same single dependency bug — not three separate ones.

Fix

webdav_client upstream (1.2.2) is unmaintained with no newer release, so this PR vendors it the way the repo already vendors other plugins (plugins/proxy, plugins/window_ext, ...):

  • plugins/webdav_client_fork/ — a copy of webdav_client 1.2.2 with a one-line change in lib/src/webdav_dio.dart:
    resp = await this.req<ResponseBody>(   // explicit type argument
    Passing the explicit generic keeps responseType: stream (no JSON fallback), so the ResponseBody covariance cast no longer fails.
  • pubspec.yamlwebdav_client now points at the local fork via path:.

The only functional change is the added generic type argument; everything else in the vendored package is byte-for-byte the upstream 1.2.2 source.

Testing

The only code change is the added generic type argument (verified against dio's fetch<T>/assureResponse<T> source). I could not run flutter analyze/flutter test in this environment (no Flutter SDK available), so a CI re-run on this branch would be valuable before merge.

Restore via WebDAV crashed on all platforms with:
  type '_Map<String, dynamic>' is not a subtype of type 'ResponseBody?' of 'value'

Root cause: webdav_client 1.2.2 calls `wdReadWithStream` without a type
argument on `req()`. T falls back to dynamic, and dio 5.x's `fetch<T>`
then resets `responseType` to `ResponseType.json`, so the body is decoded
into a Map and dio's `assureResponse<T>` (T=ResponseBody) fails the
covariant cast.

Backup works because it uses PUT (non-generic Response), only restore's
GET path hits the ResponseBody assertion.

Vendor webdav_client 1.2.2 into plugins/webdav_client_fork and pass the
explicit `req<ResponseBody>` type argument so the stream responseType is
kept and the cast no longer fails. No other logic changes.
@chenx-dust

Copy link
Copy Markdown

FlClash 的备份文件是一个 zip 压缩包,下载文件的响应体能够被识别为 json,并且转换为 _Map<String,dynamic>,这本身就说明响应体有问题的吧

@bighamx

bighamx commented Aug 27, 2026

Copy link
Copy Markdown
Author

"FlClash 的备份文件是一个 zip 压缩包,下载文件的响应体能够被识别为 json,并且转换为 _Map<String,dynamic>,这本身就说明响应体有问题的吧"

两点回应。

1. “响应体能转成 Map”恰好证明了根因在 webdav_client,而不是像你说的“响应体本身有问题”

read2File 走的 GET 是 req() 不带类型参数 → dio 走了 ResponseType.json。也就是说,dio 在下载文件时强制按 JSON 解析响应——不是服务器端什么“zip 被当成 json”,而是客户端把下载当 JSON 解了。此时:

  • 服务器返回正常 zip(如第三方反代服务器把 zip 包成了 JSON 形状,或网关错误体恰为 JSON),响应都是“JSON 可解析”→ 转成 _Map<String, dynamic>
  • 服务器若返回普通 zip 字节流,jsonDecode 直接 FormatException,根本走不到“转 Map”。

两种情况都指向同一个代码缺陷:二进制下载被强制 JSON 解析,而且它把真实的 HTTP 错误(401/502/网关 JSON 错误体)包装成一个看不懂的类型错抛出来。

2. 备份成功、恢复必崩,说明这是客户端类型层的问题,与你的服务器/网络无关

备份走 PUT,其 resp 声明为非泛型 Response,不经过 ResponseBody 协变断言;只有恢复的 GET 是 Response<ResponseBody>。如果是服务器响应体真有问题,备份和恢复应当一起失败——但实际只有恢复的 GET 链路上、以同一条 Dart 类型错固定复现(Win/Android 一致)。这正是客户端把下载响应类型写死、又被 dio 强制 JSON 分支接管导致的。

修复wdReadWithStreamreq() 补上显式类型参数 req<ResponseBody>,让下载保持 ResponseType.stream,dio 不再按 JSON 解析下载响应。我已在本机用 dio 5.11 对准本地 WebDAV 服务端验证了“备份 → 恢复”完整链路(fork 下不再出现该类型错)。

所以结论是:你观察到“响应体型态像 JSON”的现象没错,但它恰好是客户端错误地把下载当 JSON 做的直接证据,修复目标正是在这条链上,不依赖任何服务器行为。

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.

2 participants