Skip to content

Merge upstream v2.16.0#4

Merged
GT-610 merged 31 commits intolollipopkit:masterfrom
GT-610:merge-v2.16.0
Mar 30, 2026
Merged

Merge upstream v2.16.0#4
GT-610 merged 31 commits intolollipopkit:masterfrom
GT-610:merge-v2.16.0

Conversation

@GT-610
Copy link
Copy Markdown
Collaborator

@GT-610 GT-610 commented Mar 30, 2026

Summary by CodeRabbit

发布说明

  • 新功能

    • 新增命令执行返回结构,提供分离的 stdout/stderr 与退出元数据,同时保留返回合并输出的旧方法。
    • 新增高级 SFTP 下载便捷 API,支持进度回调、偏移/长度、目标关闭与管道调优。
    • 添加端到端运行示例程序与运行流程示例。
  • 修复

    • 改善环境变量应用与验证流程。
    • 增强 SFTP 对损坏 UTF‑8 文件名的容错解析。
    • 增强 RSA 签名类型验证。
  • 文档

    • 补充 Web 平台支持说明与替代传输建议;更新 Shell/CLI 终端存在性处理示例。

vicajilau and others added 29 commits March 20, 2026 22:47
…ty requests

Fixes TerminalStudio#102: Changed SSHChannelController.sendEnv() from void to Future<bool> to properly await environment variable setup responses and prevent the response from being misattributed to the PTY request.

BREAKING CHANGE: sendEnv() now returns Future<bool> instead of void. Callers must now await the result and validate it, similar to other channel request methods (sendPtyReq, sendAgentForwardingRequest, etc).

This ensures proper ordering and error detection when setting environment variables on shell/execute sessions with PTY allocations.
Add support for unencrypted legacy PEM EC private keys (BEGIN EC PRIVATE KEY) in SSHKeyPair.fromPem and SSHKeyPair.isEncryptedPem.\n\nIncludes regression test using issue TerminalStudio#109 sample and changelog entry.\n\nPartial fix for TerminalStudio#109.
…21-shell-stdio-terminal-guard

Update version to 2.16.0 and clarify stdio handling for CLI-only usage
Adds SSHRunResult and SSHClient.runWithResult() so callers can access output together with exitCode and exitSignal. Keeps SSHClient.run() backward compatible as a convenience wrapper that returns combined output bytes.\n\nIncludes integration coverage for successful and failing commands.\n\nCloses TerminalStudio#99.
…denv-race-condition

fix(channel): make sendEnv awaitable to prevent race condition with PTY requests
…gacy-ec-private-key-support

feat(keys): add legacy EC PRIVATE KEY PEM support (issue TerminalStudio#109)
…-with-result

feat(client): add runWithResult API and end-to-end flow example
…ownload-api-124

feat(sftp): add high-level download API and integration coverage
…onutf8-filenames-95

fix(sftp): tolerate malformed UTF-8 filenames
- **Breaking change**: Changed `SSHChannelController.sendEnv()` from `void` to `Future<bool>` to correctly handle responses to environment variable settings
- Improved shell stdio handling logic to prevent crashes in terminal-less environments
- Added support for the legacy `EC PRIVATE KEY` PEM format
- Added the `SSHClient.runWithResult()` method to retrieve command output and exit status
- Added an advanced SFTP download API and optimized performance for large file transfers
- Made SFTP filename parsing compatible with non-UTF-8 encodings
- Improved error messages on the web platform
- Added sample programs to demonstrate the complete execution flow
- Updated documentation and test cases
…ture type validation

Changed the `x11ScreenNumber` from a string type to an integer type to match the actual use case

Added RSA signature type validation to `SSHKeyPairAgent` to prevent type mismatches

Updated the relevant test cases to reflect the type change
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

将包版本更新为 2.16.0,更新 CHANGELOG 与 README 并新增示例文件(example/run_flows.dart)。实现/API变更包括:将 SSHChannelController.sendEnv 改为返回 Future 并等待请求回复;新增 SSHRunResult 与 SSHClient.runWithResult,同时调整 SSHClient.run 返回合并输出字节;添加 SftpClient.download 与 SftpFile.downloadTo 并重构 SftpFile.read 为可配置并发管线读取;SFTP 名称解析改为对损坏 UTF‑8 容错;新增对 legacy EC PRIVATE KEY PEM 的解析支持;消息读取 API 增加 allowMalformed 参数;X11 屏幕号由字符串改为整型;并为浏览器平台明确抛出 UnsupportedError 及补充测试。

Possibly related PRs

  • lollipopkit/dartssh2 PR 3 — 对 channel 请求处理与 X11/agent 请求相关的改动,与本次对 X11 屏幕号类型调整和 sendEnv 等请求‑reply 行为更改存在直接代码级关联。
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Merge upstream v2.16.0" clearly summarizes the main objective of the pull request - merging upstream version 2.16.0 changes. It is concise, specific, and directly related to the changeset which updates the package from version 2.15.0 to 2.16.0 across multiple files including CHANGELOG, README, source code, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

devin-ai-integration[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

… and stderr were confused during error handling

Fixed status error handling during file lookups in SFTP client testing

Improved terminal mode recovery logic in the password input example

Enhanced terminal detection handling in the shell example

Optimized offset handling for SFTP file reading

Improved curve ID recognition in EC key pair parsing
coderabbitai[bot]

This comment was marked as resolved.

…EC public key

fix(sftp_client_test): Ensure the test file is large enough to allow for a partial download
refactor(sftp_name): Improve UTF-8 decoding and encoding handling
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)
lib/src/ssh_key_pair.dart (1)

827-838: 建议:可考虑在推断曲线时验证长度一致性。

当前 _inferCurveId 使用 || 逻辑,只要公钥点长度或私钥长度任一匹配即返回。虽然后续的公钥点验证(lines 806-813)会捕获不一致的情况,但如果在此处显式检查两者一致性,代码意图会更清晰。

♻️ 可选的改进方案
 String? _inferCurveId(int publicPointLength, int privateKeyLength) {
-  if (publicPointLength == 65 || privateKeyLength == 32) {
+  if ((publicPointLength == 0 || publicPointLength == 65) && 
+      (privateKeyLength == 0 || privateKeyLength == 32) &&
+      (publicPointLength != 0 || privateKeyLength != 0)) {
     return 'nistp256';
   }
-  if (publicPointLength == 97 || privateKeyLength == 48) {
+  if ((publicPointLength == 0 || publicPointLength == 97) && 
+      (privateKeyLength == 0 || privateKeyLength == 48) &&
+      (publicPointLength != 0 || privateKeyLength != 0)) {
     return 'nistp384';
   }
-  if (publicPointLength == 133 || privateKeyLength == 66) {
+  if ((publicPointLength == 0 || publicPointLength == 133) && 
+      (privateKeyLength == 0 || privateKeyLength == 66) &&
+      (publicPointLength != 0 || privateKeyLength != 0)) {
     return 'nistp521';
   }
   return null;
 }

不过由于公钥点验证已经能捕获不一致的情况,当前实现也是可以接受的。

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

In `@lib/src/ssh_key_pair.dart` around lines 827 - 838, The _inferCurveId function
currently returns a curve when either publicPointLength or privateKeyLength
matches a curve (using ||); change the logic to require both lengths to match
for each curve (use && semantics) so _inferCurveId only returns 'nistp256',
'nistp384' or 'nistp521' when publicPointLength AND privateKeyLength correspond
to the same curve, leaving existing public-point validation unchanged; update
the condition checks in _inferCurveId to enforce length consistency between
publicPointLength and privateKeyLength.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/src/ssh_key_pair.dart`:
- Around line 827-838: The _inferCurveId function currently returns a curve when
either publicPointLength or privateKeyLength matches a curve (using ||); change
the logic to require both lengths to match for each curve (use && semantics) so
_inferCurveId only returns 'nistp256', 'nistp384' or 'nistp521' when
publicPointLength AND privateKeyLength correspond to the same curve, leaving
existing public-point validation unchanged; update the condition checks in
_inferCurveId to enforce length consistency between publicPointLength and
privateKeyLength.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4f456589-f91a-43a4-8e57-5c65aba04838

📥 Commits

Reviewing files that changed from the base of the PR and between bd6f8d2 and 804379f.

📒 Files selected for processing (3)
  • lib/src/sftp/sftp_name.dart
  • lib/src/ssh_key_pair.dart
  • test/src/sftp/sftp_client_test.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/src/sftp/sftp_name.dart
  • test/src/sftp/sftp_client_test.dart

@GT-610 GT-610 merged commit 3dddce9 into lollipopkit:master Mar 30, 2026
0 of 2 checks passed
@GT-610 GT-610 deleted the merge-v2.16.0 branch March 30, 2026 12:21
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

View 9 additional findings in Devin Review.

Open in Devin Review

[@falrom]: https://github.com/falrom
[@bradmartin333]: https://github.com/bradmartin333
[@Wackymax]: https://github.com/Wackymax
[@vicajilau]: https://github.com/vicajilau
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Removed CHANGELOG link definitions for contributors still referenced in text

The link definitions for [@shihuili1218] and [@isegal] were removed from the CHANGELOG footer, but both are still referenced in the 2.14.0 section (lines 20-21). This results in broken markdown links — the rendered text will show raw [@shihuili1218] and [@isegal] brackets instead of clickable hyperlinks.

Suggested change
[@vicajilau]: https://github.com/vicajilau
[@vicajilau]: https://github.com/vicajilau
[@shihuili1218]: https://github.com/shihuili1218
[@isegal]: https://github.com/isegal
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +643 to 669
while (bytesRead < length) {
while (
reservedOffset < endOffset && pendingReads.length < maxPendingRequests) {
final requestLength = min(chunkSize, endOffset - reservedOffset);
pendingReads.add((reservedOffset, _readChunk(requestLength, reservedOffset)));
reservedOffset += requestLength;
}

if (chunk == null) {
streamController.close();
return;
if (pendingReads.isEmpty) break;

final (startOffset, readFuture) = pendingReads.removeAt(0);
final chunk = await readFuture;
if (chunk == null) break;
if (chunk.isEmpty) {
throw SftpError('Unexpected empty data chunk before EOF');
}

streamController.add(chunk);
bytessRecieved += chunkLength;
final remaining = length - bytesRead;
final outputChunk = chunk.length <= remaining
? chunk
: Uint8List.sublistView(chunk, 0, remaining);

if (onProgress != null) onProgress(bytessRecieved);
yield outputChunk;

if (bytessRecieved >= length) {
streamController.close();
return;
}
bytesRead += outputChunk.length;
onProgress?.call(bytesRead);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 SFTP read pipeline abandons in-flight futures without error handlers, causing unhandled async errors

The rewritten SftpFile.read() pipeline dispatches up to maxPendingRequests read operations in parallel but only consumes them one-by-one via pendingReads.removeAt(0). When the loop exits early — via chunk == null (EOF at line 655) or an exception from await readFuture (line 654) — the remaining futures in pendingReads are abandoned without error handlers. If any of these in-flight _readChunk futures complete with an error (e.g. SftpStatusError), they become unhandled async errors that can crash CLI apps or trigger Zone error handlers.

Comparison with old code

The old implementation used a try/catch inside each readChunk callback and routed errors to the StreamController via streamController.addError(e, st), properly handling errors from all in-flight reads even after the stream was closed. The new code lacks equivalent cleanup for abandoned futures.

Prompt for agents
In lib/src/sftp/sftp_client.dart, in the SftpFile.read() method (around lines 643-669), when the while loop breaks early (EOF, error, or stream cancellation), the remaining futures in pendingReads are abandoned. Add cleanup logic after the while loop to await (with error suppression) all remaining pending futures. For example, after the while loop on line 669, add something like:

for (final (_, future) in pendingReads) {
  future.then((_) {}, onError: (_) {});
}
pendingReads.clear();

This ensures that errors from in-flight reads don't become unhandled async errors. Alternatively, wrap the whole while loop in a try/finally block to ensure cleanup even when the loop body throws.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@coderabbitai coderabbitai bot mentioned this pull request Mar 31, 2026
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