Skip to content

fix: expose clipboard blob routes on nvhttp#643

Merged
qiin2333 merged 2 commits into
masterfrom
fix/clipboard-blob-nvhttp
May 12, 2026
Merged

fix: expose clipboard blob routes on nvhttp#643
qiin2333 merged 2 commits into
masterfrom
fix/clipboard-blob-nvhttp

Conversation

@qiin2333
Copy link
Copy Markdown
Collaborator

@qiin2333 qiin2333 commented May 11, 2026

Summary

  • keep Sunshine clipboard processing GUI-agent-centric: the user-session GUI agent still owns system clipboard access, frame encoding/decoding, PNG/text handling, and SSE/event flow
  • factor clipboard blob upload/get response building out of the confighttp-only handlers so the blob store logic can be reused safely
  • expose only the short-lived POST /api/v1/clipboard/blob and GET /api/v1/clipboard/blob/<id> blob transport on nvhttp for paired Moonlight clients
  • keep capability, item, and events on confighttp; the local GUI agent loopback flow is unchanged

Problem

The existing clipboard design intentionally routes real clipboard handling through the user-session GUI agent. The C++ service is an opaque byte bridge because it runs outside the interactive user session and should not parse clipboard payloads or touch the OS clipboard directly.

That design works for normal text and small image payloads, but large payloads use the REF + blob path: a small KIND_REF frame is sent over the encrypted control stream while the actual bytes are stored in the short-lived clipboard blob store.

Before this patch, the blob HTTP endpoints were only available through confighttp, which is the right endpoint for the local GUI agent (https://127.0.0.1:<web-ui-port>), but it is not a natural endpoint for paired remote Moonlight clients. Those clients already authenticate to nvhttp with their pairing certificate and should not need Web UI credentials just to resolve a clipboard blob reference.

Solution

This patch does not move clipboard processing into nvhttp and does not remove or replace the confighttp GUI-agent endpoints.

Instead, it keeps the original architecture intact and mirrors only the blob object transport onto nvhttp:

  • GUI agent remains the owner of local OS clipboard access and continues using confighttp for capability, item, events, and blob access on loopback
  • paired Moonlight clients can upload/download short-lived blob objects through certificate-authenticated nvhttp when they send or receive KIND_REF frames
  • the C++ service still treats clipboard frames as opaque bytes; it only stores/fetches blob bytes by id

The shared helper also validates Content-Length before reading upload bodies, so missing/invalid lengths are rejected with bad_content_length and oversized uploads are rejected with payload_too_large before allocating the full body.

Testing

  • verified no diagnostics in src/clipboard_http.h, src/clipboard_http.cpp, and src/nvhttp.cpp
  • built sunshine successfully with cmake --build build --target sunshine -j4
  • confirmed the build required the MSYS2 UCRT toolchain path to take precedence over /mingw64/bin locally

Notes

I intentionally did not remove any confighttp clipboard routes. They are the canonical local GUI-agent transport and are still required for the user-session clipboard bridge. This PR only adds a paired-client blob transport path for REF payloads.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Review Change Stack

Summary by CodeRabbit

发布说明

  • 重构

    • 改进了剪贴板blob传输的HTTP处理流程,增强了请求验证和响应管理机制
    • 加强了上传和下载请求的数据验证
  • 安全性

    • 升级blob标识符生成方式,现采用加密级随机数算法,提升安全强度

Walkthrough

重构 clipboard blob POST/GET 端点:新增 blob_response_t 并集中化辅助函数(preflight、上传响应、下载响应、JSON 工具);处理器改为认证/参数提取后委托这些函数;将 blob ID 生成替换为 OpenSSL CSPRNG;在 nvhttp 注册证书认证的 POST/GET 路由。

Changes

Blob 端点响应构造重构

Layer / File(s) Summary
Blob ID 生成器 (OpenSSL)
src/clipboard_blob_store.cpp
用 OpenSSL RAND_bytes 实现 UUID-v4 形状的 ID 生成,设置 RFC4122 版本/变体位并在 RNG 失败时抛出异常;更新包含头。
包含与响应类型定义
src/clipboard_http.cpp, src/clipboard_http.h
添加 <cctype>/<optional> 等包含并在头文件中定义公有 blob_response_t
辅助函数声明
src/clipboard_http.h
添加并导出 make_blob_upload_response(request_headers, body)make_blob_upload_preflight_response(request_headers)make_blob_get_response(id),以及模板处理器 process_blob_upload / process_blob_get
JSON 与 Content-Length 工具
src/clipboard_http.cpp
实现 json_blob_response(始终设置 Content-Type: application/json)和严格的 parse_content_length(全消耗数字解析到 uint64_t)。
上传预检(preflight)
src/clipboard_http.cpp
实现 make_blob_upload_preflight_response:要求 Content-Length,解析并拒绝超过 clipboard_blob_store::kMaxBlobBytes 的请求,返回 JSON 错误或继续。
构建上传响应实现
src/clipboard_http.cpp
实现 make_blob_upload_response:检查 config::input.clipboard_sync、验证 X-Clipboard-Mime 形状与存在性、拒绝空体或超大体,调用 clipboard_blob_store::put 并返回 JSON 成功(id/size/expires_in)或错误。
构建下载响应实现
src/clipboard_http.cpp
实现 make_blob_get_response:检查 config::input.clipboard_sync、验证 id(非空且 36 字符)、调用 clipboard_blob_store::get(id, consume=false),返回 JSON not_found 或二进制响应并设置 Content-Type/X-Content-Type-Options: nosniff/Cache-Control: no-store
请求处理器委托
src/clipboard_http.cpp
handle_blob_upload/handle_blob_get 简化为认证/参数提取后委托 process_blob_upload/process_blob_get 并写回 blob_response_t 的 status/body/headers。
nvhttp 路由注册
src/nvhttp.cpp
包含 clipboard_http.h 并在证书认证的 nvhttp 注册 POST /api/v1/clipboard/blob 与 GET /api/v1/clipboard/blob/<id> 路由,使用 process_blob_* 返回的数据构建响应。

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant nvhttp as nvhttp.Handler
  participant process_upload as clipboard_http::process_blob_upload
  participant make_upload as clipboard_http::make_blob_upload_response
  participant process_get as clipboard_http::process_blob_get
  participant make_get as clipboard_http::make_blob_get_response
  participant clipboard_sync as config::input.clipboard_sync
  participant blob_store as clipboard_blob_store

  Client->>nvhttp: POST /api/v1/clipboard/blob (headers, body)
  nvhttp->>process_upload: forward Request
  process_upload->>make_upload: headers, body
  make_upload->>clipboard_sync: check enabled
  make_upload->>blob_store: put(mime, bytes)
  blob_store-->>make_upload: result (id/size/expires_in) or error
  make_upload-->>process_upload: blob_response_t
  process_upload-->>nvhttp: status/body/headers
  nvhttp-->>Client: HTTP response

  Client->>nvhttp: GET /api/v1/clipboard/blob/<id>
  nvhttp->>process_get: forward Request
  process_get->>make_get: id
  make_get->>clipboard_sync: check enabled
  make_get->>blob_store: get(id, consume=false)
  blob_store-->>make_get: (found|not found, mime, bytes)
  make_get-->>process_get: blob_response_t
  process_get-->>nvhttp: status/body/headers
  nvhttp-->>Client: HTTP response
Loading

预估审查工作量

🎯 4 (Complex) | ⏱️ ~45 minutes

相关 PR

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地总结了主要变更:在nvhttp上暴露剪贴板blob路由。这与PR的核心目标(为配对的Moonlight客户端在nvhttp上镜像blob对象传输)完全一致。
Description check ✅ Passed 描述详尽且与变更集高度相关,清楚地阐述了问题、解决方案、测试和设计考量。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/clipboard-blob-nvhttp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/clipboard_http.cpp`:
- Around line 316-319: Check Content-Length in the request headers before
consuming req->content: validate header via req->header (reject missing/invalid
with bad_content_length, reject > allowed limit with payload_too_large) and only
then read the body (avoiding ss << req->content.rdbuf() for oversized streams);
call make_blob_upload_response after that validation and adjust the NVHTTP
mirror route logic (the same pre-check around the code handling lines ~357-369)
so large uploads are rejected early and memory isn't fully allocated upfront.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 945bd3e5-75bf-4924-8c54-82bc49b19a4e

📥 Commits

Reviewing files that changed from the base of the PR and between 91c649f and 7af464b.

📒 Files selected for processing (3)
  • src/clipboard_http.cpp
  • src/clipboard_http.h
  • src/nvhttp.cpp
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/clipboard_http.h
  • src/nvhttp.cpp
  • src/clipboard_http.cpp

Comment thread src/clipboard_http.cpp Outdated
- factor blob upload/get response building out of confighttp handlers
- reject missing, invalid, or oversized blob Content-Length before reading request bodies
- mirror clipboard blob POST/GET endpoints onto nvhttp
- let paired Moonlight clients fetch large clipboard blobs via cert auth
- keep local GUI agent clipboard flow unchanged
@qiin2333 qiin2333 force-pushed the fix/clipboard-blob-nvhttp branch from 7af464b to 5d7d4c7 Compare May 11, 2026 15:26
- clipboard_blob_store::make_id now uses OpenSSL RAND_bytes (122 bits CSPRNG
  + RFC 4122 v4 layout) instead of std::mt19937_64. The id is effectively a
  bearer capability for any HTTPS-authenticated client that learns it via a
  KIND_REF wire frame, so cryptographic strength is required.

- Add inline templated process_blob_upload / process_blob_get helpers that
  encapsulate preflight + body read + path_match extraction. confighttp and
  nvhttp now share one definition each, eliminating duplicated lambda bodies
  while still working with nvhttp's bespoke SunshineHTTPS request type.
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
src/nvhttp.cpp (1)

2430-2441: 💤 Low value

镜像路由的设计合理,但建议补充 print_req 以保持日志一致性

新增的 blob 路由依赖 nvhttp 现有的客户端证书验证(https_server.verify)作为认证手段,而 confighttp 那边使用 auth_fn 走 basic auth,这种分工与 PR 描述一致。process_blob_upload 内部已先按 Content-Length 预检,再读 body,因此不会被超大上传压垮内存。

不过该 lambda 与 nvhttp 中其他 handler 不同,没有调用 print_req<SunshineHTTPS>(request),会导致 debug/verbose 日志里缺少这两条端点的请求记录,调试 Moonlight 客户端的 blob 走向时不便。建议补一行:

♻️ 建议补充日志
     https_server.resource["^/api/v1/clipboard/blob$"]["POST"] = [](resp_https_t response, req_https_t request) {
+      print_req<SunshineHTTPS>(request);
       auto out = clipboard_http::process_blob_upload(request);
       response->write(out.status, out.body, out.headers);
     };
     https_server.resource["^/api/v1/clipboard/blob/([A-Za-z0-9_\\-]{1,128})$"]["GET"] = [](resp_https_t response, req_https_t request) {
+      print_req<SunshineHTTPS>(request);
       auto out = clipboard_http::process_blob_get(request);
       response->write(out.status, out.body, out.headers);
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/nvhttp.cpp` around lines 2430 - 2441, The two new nvhttp route handlers
for "/api/v1/clipboard/blob" and
"/api/v1/clipboard/blob/([A-Za-z0-9_\\-]{1,128})" should call the existing
request-logging helper to keep logs consistent; inside each lambda (the POST
handler that calls clipboard_http::process_blob_upload and the GET handler that
calls clipboard_http::process_blob_get) add a call to
print_req<SunshineHTTPS>(request) before invoking the process_* functions so
those requests appear in debug/verbose logs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/nvhttp.cpp`:
- Around line 2430-2441: The two new nvhttp route handlers for
"/api/v1/clipboard/blob" and "/api/v1/clipboard/blob/([A-Za-z0-9_\\-]{1,128})"
should call the existing request-logging helper to keep logs consistent; inside
each lambda (the POST handler that calls clipboard_http::process_blob_upload and
the GET handler that calls clipboard_http::process_blob_get) add a call to
print_req<SunshineHTTPS>(request) before invoking the process_* functions so
those requests appear in debug/verbose logs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 080f78b0-9972-4e86-b2c5-88ab6e13ced6

📥 Commits

Reviewing files that changed from the base of the PR and between 5d7d4c7 and dee15ad.

📒 Files selected for processing (4)
  • src/clipboard_blob_store.cpp
  • src/clipboard_http.cpp
  • src/clipboard_http.h
  • src/nvhttp.cpp
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.{cpp,c,h}

⚙️ CodeRabbit configuration file

src/**/*.{cpp,c,h}: Sunshine 核心 C++ 源码,自托管游戏串流服务器。审查要点:内存安全、 线程安全、RAII 资源管理、安全漏洞。注意预处理宏控制的平台相关代码。

Files:

  • src/clipboard_blob_store.cpp
  • src/clipboard_http.h
  • src/nvhttp.cpp
  • src/clipboard_http.cpp
🔇 Additional comments (5)
src/clipboard_http.h (1)

89-97: 预检在前、读取在后的设计正确,符合内存安全要求

process_blob_upload 在调用 make_blob_upload_preflight_response 通过 Content-Length 拦截过大请求后再读取 body,避免了为超限请求分配内存;同时通过模板适配 SimpleWeb::HTTPSSunshineHTTPS 两种 Request 类型,确实做到了 confighttp 与 nvhttp 共用同一份逻辑。

src/clipboard_blob_store.cpp (1)

33-63: CSPRNG 替换 mt19937 的实现正确,注释也明确说明了为何必须是密码学安全 RNG

raw[6] & 0x0F | 0x40raw[8] & 0x3F | 0x80 正确设置了 RFC 4122 v4 版本/变体位,kSlots 中的 16 个位置也对应 UUID 字符串中除连字符外的字节偏移。RAND_bytes 失败时抛出 std::runtime_error,由 put()std::lock_guard 的 RAII 析构释放 g_mu,符合异常安全。鉴于注释说明 OpenSSL RNG 不可用时 TLS 栈本身也已经无法工作,硬失败是合理选择。

src/clipboard_http.cpp (3)

305-346: helper 拆分与处理器委托清晰,已正确解决先前关于先读后检的反馈

json_blob_response/parse_content_length 抽离合理:parse_content_lengthstd::all_of(isdigit) 先排除 +/空白/负号,再用 std::stoull 配合 consumed == value.size() 校验整段消耗,并以 try/catch(...) 兜住 std::out_of_range,对溢出和畸形输入都是稳健的。handle_blob_upload 现在只做 auth + 委托给 process_blob_upload,由后者先跑预检再读 body,与之前 review 中提到的「先读后检会让超大上传先占用内存」一致。


349-424: 💤 Low value

Content-Length 必填会拒绝分块传输上传,建议确认 Moonlight/curl 调用方都会发送 Content-Length

make_blob_upload_preflight_response 在没有 Content-Length header 时直接返回 bad_content_length,这对正常 Moonlight 客户端没问题,但任何使用 Transfer-Encoding: chunked 的客户端(一些 HTTP 库默认就是 chunked)都会被拒。如果产品形态确认 nvhttp 这条新路由的调用方都会显式带 Content-Length,这层严格性是可接受的安全权衡;如果未来要兼容 chunked 上传,则需在读取 body 后再二次检查实际字节数。

另外一个小观察:在 make_blob_upload_response 中预检会在 clipboard_sync 开关之前执行(Line 353-360),因此 clipboard_sync 关闭时仍会先暴露 bad_content_length 这种字段层面的错误信息——影响很小,但如果想严格做信息隐藏,可考虑把 clipboard_sync 检查前移。


426-462: GET 路径的安全防护到位

id.size() != 36 校验配合 clipboard_blob_store::make_id 产出的 UUID-v4 形状(36 字符)一致,避免路由正则 [A-Za-z0-9_\-]{1,128} 被滥用。Content-Type 回显的是 upload 时已通过 is_valid_mime 严格校验过的值,再叠加 X-Content-Type-Options: nosniffCache-Control: no-store,防御 UA sniff 与中间缓存都已覆盖;consume=false 也保留了短时重试能力,TTL 兜底回收。

@qiin2333 qiin2333 merged commit 78c069c into master May 12, 2026
4 checks passed
@qiin2333 qiin2333 deleted the fix/clipboard-blob-nvhttp branch May 12, 2026 01:32
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.

1 participant