diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..641a53f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "weekly" + time: "09:00" + timezone: Europe/Madrid \ No newline at end of file diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 60bebeb..6ee41a6 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -1,10 +1,6 @@ name: Dart -on: - push: - branches: [master] - pull_request: - branches: [master] +on: push jobs: test: @@ -13,11 +9,10 @@ jobs: strategy: matrix: os: [ubuntu-latest] - # sdk: [stable, beta, dev, 2.10.3, 2.12.0-29.10.beta] - sdk: [stable, dev] + sdk: [stable] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 with: sdk: ${{ matrix.sdk }} @@ -43,4 +38,11 @@ jobs: run: dart pub global run coverage:test_with_coverage - name: Upload coverage - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true # optional (default = false) + files: ./coverage1.xml,./coverage2.xml # optional + flags: unittests # optional + name: codecov-umbrella # optional + token: ${{ secrets.CODECOV_TOKEN }} # required + verbose: true # optional (default = false) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3c51f..4ba7e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [3.0.1] - 2024-11-05 +- Merged with 104910b1bde52ea304bffaa97d6f2487353c974d from https://github.com/TerminalStudio/dartssh2.git + ## [3.0.0] - 2024-08-13 - Changed version constraints for package `pinenacl` from `^0.5.0` to `">=0.5.0 <1.0.0"` to support Dart SDK `>= 3.5.0`. See Dart SDK [Changelog](https://github.com/dart-lang/sdk/blob/3ccadc5c277a6c70f207d14600524578f4c527ad/CHANGELOG.md?plain=1#L106). @@ -168,6 +171,11 @@ - Initial release. +[#101]: https://github.com/TerminalStudio/dartssh2/pull/101 +[#100]: https://github.com/TerminalStudio/dartssh2/issues/100 +[#80]: https://github.com/TerminalStudio/dartssh2/issues/80 +[#71]: https://github.com/TerminalStudio/dartssh2/issues/71 +[#50]: https://github.com/TerminalStudio/dartssh2/issues/50 [#24]: https://github.com/TerminalStudio/dartssh2/issues/24 [#21]: https://github.com/TerminalStudio/dartssh2/issues/21 [#18]: https://github.com/TerminalStudio/dartssh2/issues/18 @@ -177,4 +185,4 @@ [@linhanyu]: https://github.com/linhanyu [@Migarl]: https://github.com/Migarl -[@PIDAMI]: https://github.com/PIDAMI \ No newline at end of file +[@PIDAMI]: https://github.com/PIDAMI diff --git a/README.md b/README.md index a4bbfb7..320da78 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,23 @@ -

-

DartSSH 3

-

+

DartSSH 3

-

+

- + - + - - + + - + + +

-

+

SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use.

@@ -38,29 +36,39 @@ SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as e - - + + + +
+ ServerBox + Ssh! No Ports + DartShell +
- - + ServerBox interface displaying connection management options + ServerBox user interface for server control and monitoring - + Ssh! No Ports demo showcasing SSH connectivity without open ports + + + dartShell displaying terminal and session information for SSH operations
+ > Feel free to add your own app here by opening a pull request. @@ -341,20 +349,20 @@ print('free: ${statvfs.blockSize * statvfs.freeBlocks}'); ### SSH client: -- [example/example.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/example.dart) -- [example/execute.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/execute.dart) -- [example/forward_local.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/forward_local.dart) -- [example/forward_remote.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/forward_remote.dart) -- [example/pubkey.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/pubkey.dart) -- [example/shell.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/shell.dart) -- [example/ssh_jump.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/ssh_jump.dart) +- [example/example.dart](https://github.com/obemu/dartssh3/blob/master/example/example.dart) +- [example/execute.dart](https://github.com/obemu/dartssh3/blob/master/example/execute.dart) +- [example/forward_local.dart](https://github.com/obemu/dartssh3/blob/master/example/forward_local.dart) +- [example/forward_remote.dart](https://github.com/obemu/dartssh3/blob/master/example/forward_remote.dart) +- [example/pubkey.dart](https://github.com/obemu/dartssh3/blob/master/example/pubkey.dart) +- [example/shell.dart](https://github.com/obemu/dartssh3/blob/master/example/shell.dart) +- [example/ssh_jump.dart](https://github.com/obemu/dartssh3/blob/master/example/ssh_jump.dart) ### SFTP: -- [example/sftp_read.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/sftp_read.dart) -- [example/sftp_list.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/sftp_list.dart) -- [example/sftp_stat.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/sftp_stat.dart) -- [example/sftp_upload.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/sftp_upload.dart) -- [example/sftp_filetype.dart](https://github.com/TerminalStudio/dartssh2/blob/master/example/sftp_filetype.dart) +- [example/sftp_read.dart](https://github.com/obemu/dartssh3/blob/master/example/sftp_read.dart) +- [example/sftp_list.dart](https://github.com/obemu/dartssh3/blob/master/example/sftp_list.dart) +- [example/sftp_stat.dart](https://github.com/obemu/dartssh3/blob/master/example/sftp_stat.dart) +- [example/sftp_upload.dart](https://github.com/obemu/dartssh3/blob/master/example/sftp_upload.dart) +- [example/sftp_filetype.dart](https://github.com/obemu/dartssh3/blob/master/example/sftp_filetype.dart) @@ -385,45 +393,45 @@ print('free: ${statvfs.blockSize * statvfs.freeBlocks}'); **Private key**: | **Type** | **Decode** | **Decrypt** | **Encode** | **Encrypt** | -| ------------------- | ---------- | ----------- | ---------- | ----------- | -| **RSA** | ✔️ | ✔️ | ✔️ | WIP | -| **OpenSSH RSA** | ✔️ | ✔️ | ✔️ | WIP | -| **OpenSSH ECDSA** | ✔️ | ✔️ | ✔️ | WIP | -| **OpenSSH Ed25519** | ✔️ | ✔️ | ✔️ | WIP | +|---------------------|------------|-------------|------------|-------------| +| **RSA** | ✔️ | ✔️ | ✔️ | WIP | +| **OpenSSH RSA** | ✔️ | ✔️ | ✔️ | WIP | +| **OpenSSH ECDSA** | ✔️ | ✔️ | ✔️ | WIP | +| **OpenSSH Ed25519** | ✔️ | ✔️ | ✔️ | WIP | ## ⏳ Roadmap -- [x] Fix broken tests -- [x] Sound null safety +- [x] Fix broken tests. +- [x] Sound null safety. - [x] Redesign API to allow starting multiple sessions. -- [x] Full SFTP -- [ ] Server +- [x] Full SFTP. +- [ ] Server. ## References -- [`RFC 4250`](https://datatracker.ietf.org/doc/html/rfc4250) The Secure Shell (SSH) Protocol Assigned Numbers -- [`RFC 4251`](https://datatracker.ietf.org/doc/html/rfc4251) The Secure Shell (SSH) Protocol Architecture -- [`RFC 4252`](https://datatracker.ietf.org/doc/html/rfc4252) The Secure Shell (SSH) Authentication Protocol -- [`RFC 4253`](https://datatracker.ietf.org/doc/html/rfc4253) The Secure Shell (SSH) Transport Layer Protocol -- [`RFC 4254`](https://datatracker.ietf.org/doc/html/rfc4254) The Secure Shell (SSH) Connection Protocol -- [`RFC 4255`](https://datatracker.ietf.org/doc/html/rfc4255) Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints -- [`RFC 4256`](https://datatracker.ietf.org/doc/html/rfc4256) Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) -- [`RFC 4419`](https://datatracker.ietf.org/doc/html/rfc4419) Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol -- [`RFC 4716`](https://datatracker.ietf.org/doc/html/rfc4716) The Secure Shell (SSH) Public Key File Format -- [`RFC 5656`](https://datatracker.ietf.org/doc/html/rfc5656) Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer -- [`RFC 8332`](https://datatracker.ietf.org/doc/html/rfc8332) Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol -- [`RFC 8731`](https://datatracker.ietf.org/doc/html/rfc8731) Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448 -- [`draft-miller-ssh-agent-03`](https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-03) SSH Agent Protocol -- [`draft-ietf-secsh-filexfer-02`](https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02) SSH File Transfer Protocol -- [`draft-dbider-sha2-mac-for-ssh-06`](https://datatracker.ietf.org/doc/html/draft-dbider-sha2-mac-for-ssh-06) SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol +- [`RFC 4250`](https://datatracker.ietf.org/doc/html/rfc4250) The Secure Shell (SSH) Protocol Assigned Numbers. +- [`RFC 4251`](https://datatracker.ietf.org/doc/html/rfc4251) The Secure Shell (SSH) Protocol Architecture. +- [`RFC 4252`](https://datatracker.ietf.org/doc/html/rfc4252) The Secure Shell (SSH) Authentication Protocol. +- [`RFC 4253`](https://datatracker.ietf.org/doc/html/rfc4253) The Secure Shell (SSH) Transport Layer Protocol. +- [`RFC 4254`](https://datatracker.ietf.org/doc/html/rfc4254) The Secure Shell (SSH) Connection Protocol. +- [`RFC 4255`](https://datatracker.ietf.org/doc/html/rfc4255) Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints. +- [`RFC 4256`](https://datatracker.ietf.org/doc/html/rfc4256) Generic Message Exchange Authentication for the Secure Shell Protocol (SSH). +- [`RFC 4419`](https://datatracker.ietf.org/doc/html/rfc4419) Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer Protocol. +- [`RFC 4716`](https://datatracker.ietf.org/doc/html/rfc4716) The Secure Shell (SSH) Public Key File Format. +- [`RFC 5656`](https://datatracker.ietf.org/doc/html/rfc5656) Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer. +- [`RFC 8332`](https://datatracker.ietf.org/doc/html/rfc8332) Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol. +- [`RFC 8731`](https://datatracker.ietf.org/doc/html/rfc8731) Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448. +- [`draft-miller-ssh-agent-03`](https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-03) SSH Agent Protocol. +- [`draft-ietf-secsh-filexfer-02`](https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02) SSH File Transfer Protocol. +- [`draft-dbider-sha2-mac-for-ssh-06`](https://datatracker.ietf.org/doc/html/draft-dbider-sha2-mac-for-ssh-06) SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol. ## Credits -https://github.com/GreenAppers/dartssh by GreenAppers +- [https://github.com/GreenAppers/dartssh](https://github.com/GreenAppers/dartssh) by GreenAppers. -https://github.com/TerminalStudio/dartssh2 by TerminalStudio +- [https://github.com/TerminalStudio/dartssh2](https://github.com/TerminalStudio/dartssh2) by TerminalStudio ## License diff --git a/analysis_options.yaml b/analysis_options.yaml index 39158cf..20e134a 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,25 +3,8 @@ include: package:lints/recommended.yaml linter: rules: prefer_function_declarations_over_variables: false + prefer_relative_imports: true analyzer: plugins: - - dart_code_metrics - -dart_code_metrics: - anti-patterns: - # - long-method - # - long-parameter-list - metrics: - cyclomatic-complexity: 20 - maximum-nesting-level: 5 - number-of-parameters: 4 - source-lines-of-code: 50 - metrics-exclude: - - test/** - rules: - # - no-boolean-literal-compare - # - no-empty-block - - prefer-trailing-comma - - prefer-conditional-expressions - - no-equal-then-else + - dart_code_metrics_presets diff --git a/lib/src/algorithm/ssh_cipher_type.dart b/lib/src/algorithm/ssh_cipher_type.dart index dad77fa..5461f4c 100644 --- a/lib/src/algorithm/ssh_cipher_type.dart +++ b/lib/src/algorithm/ssh_cipher_type.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_algorithm.dart'; +import '../ssh_algorithm.dart'; import 'package:pointycastle/export.dart'; class SSHCipherType with SSHAlgorithm { diff --git a/lib/src/algorithm/ssh_hostkey_type.dart b/lib/src/algorithm/ssh_hostkey_type.dart index aaffe18..f626a40 100644 --- a/lib/src/algorithm/ssh_hostkey_type.dart +++ b/lib/src/algorithm/ssh_hostkey_type.dart @@ -1,4 +1,4 @@ -import 'package:dartssh3/src/ssh_algorithm.dart'; +import '../ssh_algorithm.dart'; class SSHHostkeyType with SSHAlgorithm { static const rsaSha1 = SSHHostkeyType._('ssh-rsa'); diff --git a/lib/src/algorithm/ssh_kex_type.dart b/lib/src/algorithm/ssh_kex_type.dart index 141d5af..29b7c5d 100644 --- a/lib/src/algorithm/ssh_kex_type.dart +++ b/lib/src/algorithm/ssh_kex_type.dart @@ -1,4 +1,4 @@ -import 'package:dartssh3/src/ssh_algorithm.dart'; +import '../ssh_algorithm.dart'; import 'package:pointycastle/export.dart'; class SSHKexType with SSHAlgorithm { diff --git a/lib/src/algorithm/ssh_mac_type.dart b/lib/src/algorithm/ssh_mac_type.dart index 95bcbed..3c463a3 100644 --- a/lib/src/algorithm/ssh_mac_type.dart +++ b/lib/src/algorithm/ssh_mac_type.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_algorithm.dart'; +import '../ssh_algorithm.dart'; import 'package:pointycastle/export.dart'; class SSHMacType with SSHAlgorithm { diff --git a/lib/src/hostkey/hostkey_ecdsa.dart b/lib/src/hostkey/hostkey_ecdsa.dart index 16c9af6..113d092 100644 --- a/lib/src/hostkey/hostkey_ecdsa.dart +++ b/lib/src/hostkey/hostkey_ecdsa.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:dartssh3/src/ssh_hostkey.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_hostkey.dart'; +import '../ssh_message.dart'; import 'package:pointycastle/export.dart'; class SSHEcdsaPublicKey implements SSHHostKey { diff --git a/lib/src/hostkey/hostkey_ed25519.dart b/lib/src/hostkey/hostkey_ed25519.dart index b3e1737..4c05ddc 100644 --- a/lib/src/hostkey/hostkey_ed25519.dart +++ b/lib/src/hostkey/hostkey_ed25519.dart @@ -1,6 +1,6 @@ import 'package:convert/convert.dart'; -import 'package:dartssh3/src/ssh_hostkey.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_hostkey.dart'; +import '../ssh_message.dart'; import 'package:pinenacl/ed25519.dart'; class SSHEd25519PublicKey implements SSHHostKey { diff --git a/lib/src/hostkey/hostkey_rsa.dart b/lib/src/hostkey/hostkey_rsa.dart index adef177..bbcbec1 100644 --- a/lib/src/hostkey/hostkey_rsa.dart +++ b/lib/src/hostkey/hostkey_rsa.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:dartssh3/src/ssh_hostkey.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_hostkey.dart'; +import '../ssh_message.dart'; import 'package:pinenacl/ed25519.dart'; import 'package:pointycastle/api.dart' hide Signature; diff --git a/lib/src/http/http_client.dart b/lib/src/http/http_client.dart index 3423892..489ba2f 100644 --- a/lib/src/http/http_client.dart +++ b/lib/src/http/http_client.dart @@ -1,12 +1,12 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:dartssh3/src/http/http_exception.dart'; -import 'package:dartssh3/src/http/line_decoder.dart'; -import 'package:dartssh3/src/http/http_content_type.dart'; -import 'package:dartssh3/src/http/http_headers.dart'; -import 'package:dartssh3/src/socket/ssh_socket.dart'; -import 'package:dartssh3/src/ssh_client.dart'; +import 'http_exception.dart'; +import 'line_decoder.dart'; +import 'http_content_type.dart'; +import 'http_headers.dart'; +import '../socket/ssh_socket.dart'; +import '../ssh_client.dart'; /// A HTTP client that works over SSH port forwarding. /// diff --git a/lib/src/http/http_content_type.dart b/lib/src/http/http_content_type.dart index 0b15655..5f694f6 100644 --- a/lib/src/http/http_content_type.dart +++ b/lib/src/http/http_content_type.dart @@ -1,7 +1,7 @@ import 'dart:collection'; -import 'package:dartssh3/src/http/http_exception.dart'; -import 'package:dartssh3/src/http/http_headers.dart'; +import 'http_exception.dart'; +import 'http_headers.dart'; /// A MIME/IANA media type used as the value of the /// [SSHHttpHeaders.contentTypeHeader] header. diff --git a/lib/src/http/http_headers.dart b/lib/src/http/http_headers.dart index 8b2db47..31b0767 100644 --- a/lib/src/http/http_headers.dart +++ b/lib/src/http/http_headers.dart @@ -1,4 +1,4 @@ -import 'package:dartssh3/src/http/http_content_type.dart'; +import 'http_content_type.dart'; /// Headers for HTTP requests and responses. /// diff --git a/lib/src/kex/kex_dh.dart b/lib/src/kex/kex_dh.dart index bb51bc8..a658b6c 100644 --- a/lib/src/kex/kex_dh.dart +++ b/lib/src/kex/kex_dh.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_kex.dart'; -import 'package:dartssh3/src/utils/bigint.dart'; -import 'package:dartssh3/src/utils/list.dart'; +import '../ssh_kex.dart'; +import '../utils/bigint.dart'; +import '../utils/list.dart'; /// The Diffie-Hellman (DH) key exchange provides a shared secret that /// cannot be determined by either party alone. diff --git a/lib/src/kex/kex_nist.dart b/lib/src/kex/kex_nist.dart index 6d523eb..dc0793a 100644 --- a/lib/src/kex/kex_nist.dart +++ b/lib/src/kex/kex_nist.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_kex.dart'; -import 'package:dartssh3/src/utils/bigint.dart'; -import 'package:dartssh3/src/utils/list.dart'; +import '../ssh_kex.dart'; +import '../utils/bigint.dart'; +import '../utils/list.dart'; import 'package:pointycastle/ecc/curves/secp256r1.dart'; import 'package:pointycastle/ecc/curves/secp384r1.dart'; import 'package:pointycastle/ecc/curves/secp521r1.dart'; diff --git a/lib/src/kex/kex_x25519.dart b/lib/src/kex/kex_x25519.dart index f61ba25..20bbd04 100644 --- a/lib/src/kex/kex_x25519.dart +++ b/lib/src/kex/kex_x25519.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_kex.dart'; -import 'package:dartssh3/src/utils/bigint.dart'; -import 'package:dartssh3/src/utils/list.dart'; +import '../ssh_kex.dart'; +import '../utils/bigint.dart'; +import '../utils/list.dart'; import 'package:pinenacl/tweetnacl.dart'; class SSHKexX25519 implements SSHKexECDH { diff --git a/lib/src/message/msg_channel.dart b/lib/src/message/msg_channel.dart index b9da5f5..726b62a 100644 --- a/lib/src/message/msg_channel.dart +++ b/lib/src/message/msg_channel.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; /// Message to request opening a channel to remote host. class SSH_Message_Channel_Open implements SSHMessage { diff --git a/lib/src/message/msg_debug.dart b/lib/src/message/msg_debug.dart index 4e3ded0..05e21e0 100644 --- a/lib/src/message/msg_debug.dart +++ b/lib/src/message/msg_debug.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_Debug implements SSHMessage { static const messageId = 4; diff --git a/lib/src/message/msg_disconnect.dart b/lib/src/message/msg_disconnect.dart index 5f1fc2c..e206646 100644 --- a/lib/src/message/msg_disconnect.dart +++ b/lib/src/message/msg_disconnect.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_Disconnect extends SSHMessage { static const messageId = 1; diff --git a/lib/src/message/msg_ignore.dart b/lib/src/message/msg_ignore.dart index 2028a28..5c3a0db 100644 --- a/lib/src/message/msg_ignore.dart +++ b/lib/src/message/msg_ignore.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_Ignore extends SSHMessage { static const messageId = 2; diff --git a/lib/src/message/msg_kex.dart b/lib/src/message/msg_kex.dart index fb2bb02..c4d4180 100644 --- a/lib/src/message/msg_kex.dart +++ b/lib/src/message/msg_kex.dart @@ -2,8 +2,8 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; -import 'package:dartssh3/src/utils/list.dart'; +import '../ssh_message.dart'; +import '../utils/list.dart'; class SSH_Message_KexInit implements SSHMessage { static const messageId = 20; diff --git a/lib/src/message/msg_kex_dh.dart b/lib/src/message/msg_kex_dh.dart index 61cd4a5..6d7c01d 100644 --- a/lib/src/message/msg_kex_dh.dart +++ b/lib/src/message/msg_kex_dh.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_KexDH_Init implements SSHMessage { static const messageId = 30; diff --git a/lib/src/message/msg_kex_ecdh.dart b/lib/src/message/msg_kex_ecdh.dart index e6e5705..7724d29 100644 --- a/lib/src/message/msg_kex_ecdh.dart +++ b/lib/src/message/msg_kex_ecdh.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_KexECDH_Init implements SSHMessage { static const messageId = 30; diff --git a/lib/src/message/msg_request.dart b/lib/src/message/msg_request.dart index 41005b0..fa5a253 100644 --- a/lib/src/message/msg_request.dart +++ b/lib/src/message/msg_request.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_Global_Request extends SSHMessage { static const messageId = 80; diff --git a/lib/src/message/msg_service.dart b/lib/src/message/msg_service.dart index c0d7104..2ef702c 100644 --- a/lib/src/message/msg_service.dart +++ b/lib/src/message/msg_service.dart @@ -2,7 +2,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../ssh_message.dart'; class SSH_Message_Service_Request implements SSHMessage { static const messageId = 5; diff --git a/lib/src/message/msg_userauth.dart b/lib/src/message/msg_userauth.dart index 3416b5d..59bd421 100644 --- a/lib/src/message/msg_userauth.dart +++ b/lib/src/message/msg_userauth.dart @@ -2,8 +2,8 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; -import 'package:dartssh3/src/ssh_userauth.dart'; +import '../ssh_message.dart'; +import '../ssh_userauth.dart'; class SSH_Message_Userauth_Request extends SSHMessage { static const messageId = 50; diff --git a/lib/src/sftp/sftp_client.dart b/lib/src/sftp/sftp_client.dart index ad6d28f..833c439 100644 --- a/lib/src/sftp/sftp_client.dart +++ b/lib/src/sftp/sftp_client.dart @@ -3,19 +3,19 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:dartssh3/src/sftp/sftp_errors.dart'; -import 'package:dartssh3/src/sftp/sftp_file_attrs.dart'; -import 'package:dartssh3/src/sftp/sftp_file_open_mode.dart'; -import 'package:dartssh3/src/sftp/sftp_name.dart'; -import 'package:dartssh3/src/sftp/sftp_packet.dart'; -import 'package:dartssh3/src/sftp/sftp_packet_ext.dart'; -import 'package:dartssh3/src/sftp/sftp_request_id.dart'; -import 'package:dartssh3/src/sftp/sftp_statvfs.dart'; -import 'package:dartssh3/src/sftp/sftp_stream_io.dart'; -import 'package:dartssh3/src/ssh_channel.dart'; -import 'package:dartssh3/src/ssh_transport.dart'; -import 'package:dartssh3/src/utils/chunk_buffer.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'sftp_errors.dart'; +import 'sftp_file_attrs.dart'; +import 'sftp_file_open_mode.dart'; +import 'sftp_name.dart'; +import 'sftp_packet.dart'; +import 'sftp_packet_ext.dart'; +import 'sftp_request_id.dart'; +import 'sftp_statvfs.dart'; +import 'sftp_stream_io.dart'; +import '../ssh_channel.dart'; +import '../ssh_transport.dart'; +import '../utils/chunk_buffer.dart'; +import '../ssh_message.dart'; const _kVersion = 3; diff --git a/lib/src/sftp/sftp_errors.dart b/lib/src/sftp/sftp_errors.dart index 3bf2db7..e50afeb 100644 --- a/lib/src/sftp/sftp_errors.dart +++ b/lib/src/sftp/sftp_errors.dart @@ -1,5 +1,5 @@ -import 'package:dartssh3/src/sftp/sftp_packet.dart'; -import 'package:dartssh3/src/sftp/sftp_status_code.dart'; +import 'sftp_packet.dart'; +import 'sftp_status_code.dart'; class SftpError { final String message; diff --git a/lib/src/sftp/sftp_file_attrs.dart b/lib/src/sftp/sftp_file_attrs.dart index 8207d11..bf704a9 100644 --- a/lib/src/sftp/sftp_file_attrs.dart +++ b/lib/src/sftp/sftp_file_attrs.dart @@ -1,5 +1,5 @@ -import 'package:dartssh3/src/utils/int.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import '../utils/int.dart'; +import '../ssh_message.dart'; abstract class _Flags { static const size = 0x00000001; diff --git a/lib/src/sftp/sftp_file_open_mode.dart b/lib/src/sftp/sftp_file_open_mode.dart index 6632c9a..824fd4a 100644 --- a/lib/src/sftp/sftp_file_open_mode.dart +++ b/lib/src/sftp/sftp_file_open_mode.dart @@ -24,10 +24,34 @@ class SftpFileOpenMode { /// [create] MUST also be specified if this flag is used. static const exclusive = SftpFileOpenMode._(1 << 5); + /// Internal integer flag representing the file open mode. final int flag; + /// Private constructor used to create instances of [SftpFileOpenMode] with specific flags. + /// + /// This constructor is marked as private (`._`) to restrict direct instantiation and ensure + /// that only predefined modes like [read], [write], etc., can be used. const SftpFileOpenMode._(this.flag); + /// Overloads the bitwise OR operator `|` for the `SftpFileOpenMode` class. + /// + /// This operator allows combining two `SftpFileOpenMode` instances by performing + /// a bitwise OR operation on their respective flags. The result is a new + /// `SftpFileOpenMode` instance that represents the combined flags of both modes. + /// + /// Example: + /// ```dart + /// SftpFileOpenMode readMode = SftpFileOpenMode.read; + /// SftpFileOpenMode writeMode = SftpFileOpenMode.write; + /// + /// SftpFileOpenMode combinedMode = readMode | writeMode; + /// ``` + /// + /// In the example above, the `combinedMode` will contain the flags of both + /// `readMode` and `writeMode`. + /// + /// - Parameter [other]: Another instance of `SftpFileOpenMode` to combine with. + /// - Returns: A new `SftpFileOpenMode` instance containing the combined flags. SftpFileOpenMode operator |(SftpFileOpenMode other) => SftpFileOpenMode._(flag | other.flag); } diff --git a/lib/src/sftp/sftp_name.dart b/lib/src/sftp/sftp_name.dart index 30de0c2..77a6933 100644 --- a/lib/src/sftp/sftp_name.dart +++ b/lib/src/sftp/sftp_name.dart @@ -1,5 +1,5 @@ -import 'package:dartssh3/src/sftp/sftp_file_attrs.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'sftp_file_attrs.dart'; +import '../ssh_message.dart'; class SftpName { final String filename; diff --git a/lib/src/sftp/sftp_packet.dart b/lib/src/sftp/sftp_packet.dart index f8b6593..d905ade 100644 --- a/lib/src/sftp/sftp_packet.dart +++ b/lib/src/sftp/sftp_packet.dart @@ -1,9 +1,9 @@ import 'dart:typed_data'; import 'package:convert/convert.dart'; -import 'package:dartssh3/src/sftp/sftp_file_attrs.dart'; -import 'package:dartssh3/src/sftp/sftp_name.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'sftp_file_attrs.dart'; +import 'sftp_name.dart'; +import '../ssh_message.dart'; // SSH_FXP_INIT 1 init // SSH_FXP_VERSION 2 init-reply diff --git a/lib/src/sftp/sftp_packet_ext.dart b/lib/src/sftp/sftp_packet_ext.dart index 55a49aa..1be316c 100644 --- a/lib/src/sftp/sftp_packet_ext.dart +++ b/lib/src/sftp/sftp_packet_ext.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/sftp/sftp_packet.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'sftp_packet.dart'; +import '../ssh_message.dart'; /// Represents the payload of an extended request. Should be wrapped in a /// [SftpExtendedPacket] before being sent. diff --git a/lib/src/sftp/sftp_statvfs.dart b/lib/src/sftp/sftp_statvfs.dart index 2f7fa50..d0cc1d5 100644 --- a/lib/src/sftp/sftp_statvfs.dart +++ b/lib/src/sftp/sftp_statvfs.dart @@ -1,4 +1,4 @@ -import 'package:dartssh3/src/sftp/sftp_packet_ext.dart'; +import 'sftp_packet_ext.dart'; /// Information about the file system. Corresponds to the `statvfs` system call /// on POSIX systems. diff --git a/lib/src/sftp/sftp_stream_io.dart b/lib/src/sftp/sftp_stream_io.dart index 0b9920c..57182f0 100644 --- a/lib/src/sftp/sftp_stream_io.dart +++ b/lib/src/sftp/sftp_stream_io.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:dartssh3/src/sftp/sftp_client.dart'; -import 'package:dartssh3/src/utils/stream.dart'; +import 'sftp_client.dart'; +import '../utils/stream.dart'; /// The amount of data to send in a single SFTP packet. /// @@ -90,6 +90,14 @@ class SftpFileWriter with DoneFuture { _subscription.resume(); } + /// Handles the incoming data chunks from the stream. + /// + /// This function manages the flow control by pausing the stream if the + /// amount of unacknowledged data (`_bytesOnTheWire`) exceeds the + /// `maxBytesOnTheWire` limit. It then writes the data chunk to the remote file + /// at the appropriate offset, updates the counters, and triggers the + /// progress callback. Finally, it checks if all data has been acknowledged + /// and completes the operation if done. Future _handleLocalData(Uint8List chunk) async { if (_bytesOnTheWire >= maxBytesOnTheWire) { _subscription.pause(); @@ -108,13 +116,19 @@ class SftpFileWriter with DoneFuture { _subscription.resume(); } - if (!_doneCompleter.isCompleted && - _streamDone && - _bytesSent == _bytesAcked) { + if (_streamDone && + _bytesSent == _bytesAcked && + !_doneCompleter.isCompleted) { _doneCompleter.complete(); } } + /// Handles the completion of the data stream. + /// + /// This function is triggered when the stream has finished emitting all its + /// data. It checks if all data has been successfully acknowledged and + /// marks the operation as complete by calling `_doneCompleter.complete()` + /// if no more data remains to be processed. void _handleLocalDone() { _streamDone = true; if (_bytesSent == _bytesAcked) { @@ -125,7 +139,7 @@ class SftpFileWriter with DoneFuture { /// Implements [Future] interface for [SftpFileWriter]. /// -/// This is for compatibility with earlier versions of dartssh2. +/// This is for compatibility with earlier versions of dartssh2 and dartssh2. mixin DoneFuture implements Future { Future get done; diff --git a/lib/src/socket/ssh_socket.dart b/lib/src/socket/ssh_socket.dart index cf37bcc..6cebe81 100644 --- a/lib/src/socket/ssh_socket.dart +++ b/lib/src/socket/ssh_socket.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:dartssh3/src/socket/ssh_socket_io.dart' +import 'ssh_socket_io.dart' if (dart.library.js) 'package:dartssh3/src/socket/ssh_socket_js.dart'; abstract class SSHSocket { diff --git a/lib/src/socket/ssh_socket_io.dart b/lib/src/socket/ssh_socket_io.dart index 3e0138e..6ab05a6 100644 --- a/lib/src/socket/ssh_socket_io.dart +++ b/lib/src/socket/ssh_socket_io.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; -import 'package:dartssh3/src/socket/ssh_socket.dart'; +import 'ssh_socket.dart'; Future connectNativeSocket( String host, diff --git a/lib/src/socket/ssh_socket_js.dart b/lib/src/socket/ssh_socket_js.dart index c0d8b17..26a57cb 100644 --- a/lib/src/socket/ssh_socket_js.dart +++ b/lib/src/socket/ssh_socket_js.dart @@ -1,4 +1,4 @@ -import 'package:dartssh3/src/socket/ssh_socket.dart'; +import 'ssh_socket.dart'; Future connectNativeSocket( String host, diff --git a/lib/src/ssh_algorithm.dart b/lib/src/ssh_algorithm.dart index 1a34f5d..382b84b 100644 --- a/lib/src/ssh_algorithm.dart +++ b/lib/src/ssh_algorithm.dart @@ -1,7 +1,7 @@ -import 'package:dartssh3/src/algorithm/ssh_cipher_type.dart'; -import 'package:dartssh3/src/algorithm/ssh_hostkey_type.dart'; -import 'package:dartssh3/src/algorithm/ssh_kex_type.dart'; -import 'package:dartssh3/src/algorithm/ssh_mac_type.dart'; +import 'algorithm/ssh_cipher_type.dart'; +import 'algorithm/ssh_hostkey_type.dart'; +import 'algorithm/ssh_kex_type.dart'; +import 'algorithm/ssh_mac_type.dart'; abstract class SSHAlgorithm { /// The name of the algorithm. diff --git a/lib/src/ssh_channel.dart b/lib/src/ssh_channel.dart index dcfda81..99a3e21 100644 --- a/lib/src/ssh_channel.dart +++ b/lib/src/ssh_channel.dart @@ -3,12 +3,12 @@ import 'dart:math'; import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_channel_id.dart'; -import 'package:dartssh3/src/ssh_transport.dart'; -import 'package:dartssh3/src/utils/async_queue.dart'; -import 'package:dartssh3/src/message/msg_channel.dart'; -import 'package:dartssh3/src/ssh_message.dart'; -import 'package:dartssh3/src/utils/stream.dart'; +import 'ssh_channel_id.dart'; +import 'ssh_transport.dart'; +import 'utils/async_queue.dart'; +import 'message/msg_channel.dart'; +import 'ssh_message.dart'; +import 'utils/stream.dart'; /// Handler of channel requests. Return true if the request was handled, false /// if the request was not recognized or could not be handled. @@ -489,7 +489,7 @@ class SSHChannelDataSplitter } class SSHChannelDataConsumer extends StreamConsumerBase { - SSHChannelDataConsumer(Stream stream) : super(stream); + SSHChannelDataConsumer(super.stream); @override int getLength(SSHChannelData chunk) { diff --git a/lib/src/ssh_client.dart b/lib/src/ssh_client.dart index b4078ca..4ccf06b 100644 --- a/lib/src/ssh_client.dart +++ b/lib/src/ssh_client.dart @@ -2,25 +2,25 @@ import 'dart:async'; import 'dart:collection'; import 'dart:typed_data'; -import 'package:dartssh3/src/http/http_client.dart'; -import 'package:dartssh3/src/sftp/sftp_client.dart'; -import 'package:dartssh3/src/ssh_algorithm.dart'; -import 'package:dartssh3/src/ssh_channel.dart'; -import 'package:dartssh3/src/ssh_channel_id.dart'; -import 'package:dartssh3/src/ssh_errors.dart'; -import 'package:dartssh3/src/ssh_forward.dart'; -import 'package:dartssh3/src/ssh_keepalive.dart'; -import 'package:dartssh3/src/ssh_key_pair.dart'; -import 'package:dartssh3/src/ssh_session.dart'; -import 'package:dartssh3/src/ssh_transport.dart'; -import 'package:dartssh3/src/utils/async_queue.dart'; -import 'package:dartssh3/src/message/msg_channel.dart'; -import 'package:dartssh3/src/message/msg_request.dart'; -import 'package:dartssh3/src/message/msg_service.dart'; -import 'package:dartssh3/src/message/msg_userauth.dart'; -import 'package:dartssh3/src/ssh_message.dart'; -import 'package:dartssh3/src/socket/ssh_socket.dart'; -import 'package:dartssh3/src/ssh_userauth.dart'; +import 'http/http_client.dart'; +import 'sftp/sftp_client.dart'; +import 'ssh_algorithm.dart'; +import 'ssh_channel.dart'; +import 'ssh_channel_id.dart'; +import 'ssh_errors.dart'; +import 'ssh_forward.dart'; +import 'ssh_keepalive.dart'; +import 'ssh_key_pair.dart'; +import 'ssh_session.dart'; +import 'ssh_transport.dart'; +import 'utils/async_queue.dart'; +import 'message/msg_channel.dart'; +import 'message/msg_request.dart'; +import 'message/msg_service.dart'; +import 'message/msg_userauth.dart'; +import 'ssh_message.dart'; +import 'socket/ssh_socket.dart'; +import 'ssh_userauth.dart'; /// https://datatracker.ietf.org/doc/html/rfc4252#section-8 typedef SSHPasswordRequestHandler = FutureOr Function(); diff --git a/lib/src/ssh_errors.dart b/lib/src/ssh_errors.dart index 29f51cf..68407de 100644 --- a/lib/src/ssh_errors.dart +++ b/lib/src/ssh_errors.dart @@ -86,7 +86,7 @@ class SSHKeyDecodeError with SSHMessageError implements SSHError { /// Errors that happen when the library fails to decrypt the host key. class SSHKeyDecryptError extends SSHKeyDecodeError { - SSHKeyDecryptError(String message, [Object? error]) : super(message, error); + SSHKeyDecryptError(super.message, [super.error]); } /// Errors that happen when the library fails to open a channel. diff --git a/lib/src/ssh_forward.dart b/lib/src/ssh_forward.dart index b062dc6..6f446de 100644 --- a/lib/src/ssh_forward.dart +++ b/lib/src/ssh_forward.dart @@ -1,8 +1,8 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:dartssh3/src/socket/ssh_socket.dart'; -import 'package:dartssh3/src/ssh_channel.dart'; +import 'socket/ssh_socket.dart'; +import 'ssh_channel.dart'; class SSHForwardChannel implements SSHSocket { final SSHChannel _channel; diff --git a/lib/src/ssh_hostkey.dart b/lib/src/ssh_hostkey.dart index 609fdb9..de5ff3c 100644 --- a/lib/src/ssh_hostkey.dart +++ b/lib/src/ssh_hostkey.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'ssh_message.dart'; abstract class SSHHostKey { /// Encode the host key to SSH encoded data. diff --git a/lib/src/ssh_kex_utils.dart b/lib/src/ssh_kex_utils.dart index 1cc8ac3..a29e4c0 100644 --- a/lib/src/ssh_kex_utils.dart +++ b/lib/src/ssh_kex_utils.dart @@ -1,8 +1,8 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/kex/kex_dh.dart'; -import 'package:dartssh3/src/ssh_algorithm.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'kex/kex_dh.dart'; +import 'ssh_algorithm.dart'; +import 'ssh_message.dart'; import 'package:pointycastle/export.dart'; abstract class SSHKexUtils { diff --git a/lib/src/ssh_key_pair.dart b/lib/src/ssh_key_pair.dart index 6496370..488796a 100644 --- a/lib/src/ssh_key_pair.dart +++ b/lib/src/ssh_key_pair.dart @@ -4,15 +4,15 @@ import 'dart:typed_data'; import 'package:asn1lib/asn1lib.dart'; import 'package:convert/convert.dart'; -import 'package:dartssh3/dartssh3.dart'; -import 'package:dartssh3/src/hostkey/hostkey_ecdsa.dart'; -import 'package:dartssh3/src/hostkey/hostkey_ed25519.dart'; -import 'package:dartssh3/src/hostkey/hostkey_rsa.dart'; -import 'package:dartssh3/src/ssh_hostkey.dart'; -import 'package:dartssh3/src/ssh_message.dart'; -import 'package:dartssh3/src/utils/bcrypt.dart'; -import 'package:dartssh3/src/utils/cipher_ext.dart'; -import 'package:dartssh3/src/utils/list.dart'; +import '../dartssh3.dart'; +import 'hostkey/hostkey_ecdsa.dart'; +import 'hostkey/hostkey_ed25519.dart'; +import 'hostkey/hostkey_rsa.dart'; +import 'ssh_hostkey.dart'; +import 'ssh_message.dart'; +import 'utils/bcrypt.dart'; +import 'utils/cipher_ext.dart'; +import 'utils/list.dart'; import 'package:pinenacl/ed25519.dart' as ed25519; import 'package:pointycastle/export.dart'; diff --git a/lib/src/ssh_message.dart b/lib/src/ssh_message.dart index 0a8c99b..1c3ca8c 100644 --- a/lib/src/ssh_message.dart +++ b/lib/src/ssh_message.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:dartssh3/src/utils/int.dart'; -import 'package:dartssh3/src/utils/bigint.dart'; -import 'package:dartssh3/src/utils/utf8.dart'; +import 'utils/int.dart'; +import 'utils/bigint.dart'; +import 'utils/utf8.dart'; abstract class SSHMessage { /// Encode the message to SSH encoded data. diff --git a/lib/src/ssh_session.dart b/lib/src/ssh_session.dart index 54d7176..cc96c09 100644 --- a/lib/src/ssh_session.dart +++ b/lib/src/ssh_session.dart @@ -1,9 +1,9 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:dartssh3/src/ssh_channel.dart'; -import 'package:dartssh3/src/ssh_signal.dart'; -import 'package:dartssh3/src/message/msg_channel.dart'; +import 'ssh_channel.dart'; +import 'ssh_signal.dart'; +import 'message/msg_channel.dart'; /// A [SSHSession] represents a remote execution of a program. class SSHSession { diff --git a/lib/src/ssh_transport.dart b/lib/src/ssh_transport.dart index b178a96..ffdc160 100644 --- a/lib/src/ssh_transport.dart +++ b/lib/src/ssh_transport.dart @@ -3,32 +3,32 @@ import 'dart:convert'; import 'dart:math' show max; import 'dart:typed_data'; -import 'package:dartssh3/src/algorithm/ssh_cipher_type.dart'; -import 'package:dartssh3/src/algorithm/ssh_hostkey_type.dart'; -import 'package:dartssh3/src/algorithm/ssh_kex_type.dart'; -import 'package:dartssh3/src/algorithm/ssh_mac_type.dart'; -import 'package:dartssh3/src/hostkey/hostkey_ecdsa.dart'; -import 'package:dartssh3/src/hostkey/hostkey_rsa.dart'; -import 'package:dartssh3/src/kex/kex_dh.dart'; -import 'package:dartssh3/src/kex/kex_nist.dart'; -import 'package:dartssh3/src/kex/kex_x25519.dart'; -import 'package:dartssh3/src/message/msg_userauth.dart'; -import 'package:dartssh3/src/socket/ssh_socket.dart'; -import 'package:dartssh3/src/ssh_algorithm.dart'; -import 'package:dartssh3/src/ssh_kex.dart'; -import 'package:dartssh3/src/utils/bigint.dart'; -import 'package:dartssh3/src/utils/cipher_ext.dart'; -import 'package:dartssh3/src/utils/chunk_buffer.dart'; -import 'package:dartssh3/src/ssh_errors.dart'; -import 'package:dartssh3/src/ssh_kex_utils.dart'; -import 'package:dartssh3/src/ssh_packet.dart'; -import 'package:dartssh3/src/utils/int.dart'; -import 'package:dartssh3/src/hostkey/hostkey_ed25519.dart'; -import 'package:dartssh3/src/utils/list.dart'; -import 'package:dartssh3/src/message/msg_kex.dart'; -import 'package:dartssh3/src/message/msg_kex_dh.dart'; -import 'package:dartssh3/src/message/msg_kex_ecdh.dart'; -import 'package:dartssh3/src/ssh_message.dart'; +import 'algorithm/ssh_cipher_type.dart'; +import 'algorithm/ssh_hostkey_type.dart'; +import 'algorithm/ssh_kex_type.dart'; +import 'algorithm/ssh_mac_type.dart'; +import 'hostkey/hostkey_ecdsa.dart'; +import 'hostkey/hostkey_rsa.dart'; +import 'kex/kex_dh.dart'; +import 'kex/kex_nist.dart'; +import 'kex/kex_x25519.dart'; +import 'message/msg_userauth.dart'; +import 'socket/ssh_socket.dart'; +import 'ssh_algorithm.dart'; +import 'ssh_kex.dart'; +import 'utils/bigint.dart'; +import 'utils/cipher_ext.dart'; +import 'utils/chunk_buffer.dart'; +import 'ssh_errors.dart'; +import 'ssh_kex_utils.dart'; +import 'ssh_packet.dart'; +import 'utils/int.dart'; +import 'hostkey/hostkey_ed25519.dart'; +import 'utils/list.dart'; +import 'message/msg_kex.dart'; +import 'message/msg_kex_dh.dart'; +import 'message/msg_kex_ecdh.dart'; +import 'ssh_message.dart'; import 'package:pointycastle/export.dart'; typedef SSHPrintHandler = void Function(String?); diff --git a/lib/src/utils/stream.dart b/lib/src/utils/stream.dart index 71ef985..cf0b4ae 100644 --- a/lib/src/utils/stream.dart +++ b/lib/src/utils/stream.dart @@ -136,7 +136,7 @@ abstract class StreamConsumerBase { /// A helper class that can be used to read data from a byte stream on demand. class StreamConsumer extends StreamConsumerBase { - StreamConsumer(Stream stream) : super(stream); + StreamConsumer(super.stream); @override int getLength(Uint8List chunk) { diff --git a/pubspec.yaml b/pubspec.yaml index 1efc40a..c1e6bc3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: dartssh3 -version: 3.0.0 +version: 3.0.1 description: SSH and SFTP client written in pure Dart, aiming to be feature-rich as well as easy to use. homepage: https://github.com/obemu/dartssh3 @@ -14,10 +14,11 @@ dependencies: pinenacl: ">=0.5.0 <1.0.0" dev_dependencies: - build_runner: ^2.1.1 - dart_code_metrics: ^4.16.0 - lints: ^2.0.0 - test: ^1.6.5 + build_runner: ^2.4.12 + dart_code_metrics_presets: ^2.15.0 + lints: ">=4.0.0 <6.0.0" + test: ^1.25.8 + ansi_colorizer: any false_secrets: - test diff --git a/test/src/algorithm/ssh_cipher_type_test.dart b/test/src/algorithm/ssh_cipher_type_test.dart index 2eea1e0..aa33b22 100644 --- a/test/src/algorithm/ssh_cipher_type_test.dart +++ b/test/src/algorithm/ssh_cipher_type_test.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; -import 'package:dartssh3/src/algorithm/ssh_cipher_type.dart'; +import 'package:dartssh3/dartssh3.dart'; +import 'package:dartssh3/src/ssh_algorithm.dart'; import 'package:dartssh3/src/utils/cipher_ext.dart'; import 'package:test/test.dart'; @@ -11,6 +12,81 @@ void main() { testCipher(SSHCipherType.aes128ctr); testCipher(SSHCipherType.aes192ctr); testCipher(SSHCipherType.aes256ctr); + group('SSHAlgorithm', () { + test('toString() returns correct format', () { + final algorithm = SSHKexType.x25519; + expect(algorithm.toString(), equals(SSHKexType.x25519.toString())); + }); + }); + + group('SSHAlgorithmList extension', () { + test('toNameList() returns list of names', () { + final algorithms = [SSHKexType.x25519, SSHKexType.nistp521]; + final names = algorithms.toNameList(); + expect(names, equals([SSHKexType.x25519.name, SSHKexType.nistp521.name])); + }); + + test('getByName() returns correct algorithm', () { + final algorithms = [SSHKexType.x25519, SSHKexType.nistp521]; + final algorithm = algorithms.getByName(SSHKexType.nistp521.name); + expect(algorithm, isNotNull); + expect(algorithm!.name, equals(SSHKexType.nistp521.name)); + }); + + test('getByName() returns null when not found', () { + final algorithms = [SSHKexType.x25519, SSHKexType.nistp521]; + final algorithm = algorithms.getByName('nonexistent'); + expect(algorithm, isNull); + }); + }); + + test('Default values are set correctly', () { + final algorithms = SSHAlgorithms(); + + expect( + algorithms.kex, + equals([ + SSHKexType.x25519, + SSHKexType.nistp521, + SSHKexType.nistp384, + SSHKexType.nistp256, + SSHKexType.dhGexSha256, + SSHKexType.dh14Sha256, + SSHKexType.dh14Sha1, + SSHKexType.dhGexSha1, + SSHKexType.dh1Sha1, + ])); + + expect( + algorithms.hostkey, + equals([ + SSHHostkeyType.ed25519, + SSHHostkeyType.rsaSha512, + SSHHostkeyType.rsaSha256, + SSHHostkeyType.rsaSha1, + SSHHostkeyType.ecdsa521, + SSHHostkeyType.ecdsa384, + SSHHostkeyType.ecdsa256, + ])); + + expect( + algorithms.cipher, + equals([ + SSHCipherType.aes128ctr, + SSHCipherType.aes128cbc, + SSHCipherType.aes256ctr, + SSHCipherType.aes256cbc, + ])); + + expect( + algorithms.mac, + equals([ + SSHMacType.hmacSha1, + SSHMacType.hmacSha256, + SSHMacType.hmacSha512, + SSHMacType.hmacMd5, + ])); + }); } void testCipher(SSHCipherType type) { diff --git a/test/src/algorithm/ssh_hostkey_type_test.dart b/test/src/algorithm/ssh_hostkey_type_test.dart new file mode 100644 index 0000000..a1affd4 --- /dev/null +++ b/test/src/algorithm/ssh_hostkey_type_test.dart @@ -0,0 +1,33 @@ +import 'package:dartssh3/dartssh3.dart'; +import 'package:test/test.dart'; + +void main() { + group('SSHHostkeyType', () { + test('Static constants are defined correctly', () { + expect(SSHHostkeyType.rsaSha1.name, equals('ssh-rsa')); + expect(SSHHostkeyType.rsaSha256.name, equals('rsa-sha2-256')); + expect(SSHHostkeyType.rsaSha512.name, equals('rsa-sha2-512')); + expect(SSHHostkeyType.ecdsa256.name, equals('ecdsa-sha2-nistp256')); + expect(SSHHostkeyType.ecdsa384.name, equals('ecdsa-sha2-nistp384')); + expect(SSHHostkeyType.ecdsa521.name, equals('ecdsa-sha2-nistp521')); + expect(SSHHostkeyType.ed25519.name, equals('ssh-ed25519')); + }); + + test('toString() returns correct format', () { + expect( + SSHHostkeyType.rsaSha1.toString(), equals('SSHHostkeyType(ssh-rsa)')); + expect(SSHHostkeyType.rsaSha256.toString(), + equals('SSHHostkeyType(rsa-sha2-256)')); + expect(SSHHostkeyType.rsaSha512.toString(), + equals('SSHHostkeyType(rsa-sha2-512)')); + expect(SSHHostkeyType.ecdsa256.toString(), + equals('SSHHostkeyType(ecdsa-sha2-nistp256)')); + expect(SSHHostkeyType.ecdsa384.toString(), + equals('SSHHostkeyType(ecdsa-sha2-nistp384)')); + expect(SSHHostkeyType.ecdsa521.toString(), + equals('SSHHostkeyType(ecdsa-sha2-nistp521)')); + expect(SSHHostkeyType.ed25519.toString(), + equals('SSHHostkeyType(ssh-ed25519)')); + }); + }); +} diff --git a/test/src/algorithm/ssh_kex_type_test.dart b/test/src/algorithm/ssh_kex_type_test.dart new file mode 100644 index 0000000..663f486 --- /dev/null +++ b/test/src/algorithm/ssh_kex_type_test.dart @@ -0,0 +1,68 @@ +import 'package:dartssh3/dartssh3.dart'; +import 'package:pointycastle/export.dart'; +import 'package:test/test.dart'; + +void main() { + group('SSHKexType', () { + test('Static constants are defined correctly', () { + expect(SSHKexType.x25519.name, equals('curve25519-sha256@libssh.org')); + expect(SSHKexType.nistp256.name, equals('ecdh-sha2-nistp256')); + expect(SSHKexType.nistp384.name, equals('ecdh-sha2-nistp384')); + expect(SSHKexType.nistp521.name, equals('ecdh-sha2-nistp521')); + expect(SSHKexType.dhGexSha256.name, + equals('diffie-hellman-group-exchange-sha256')); + expect(SSHKexType.dhGexSha1.name, + equals('diffie-hellman-group-exchange-sha1')); + expect(SSHKexType.dh14Sha1.name, equals('diffie-hellman-group14-sha1')); + expect( + SSHKexType.dh14Sha256.name, equals('diffie-hellman-group14-sha256')); + expect(SSHKexType.dh1Sha1.name, equals('diffie-hellman-group1-sha1')); + }); + + test( + 'Static constants have correct digestFactory and isGroupExchange values', + () { + expect(SSHKexType.x25519.digestFactory, equals(digestSha256)); + expect(SSHKexType.nistp256.digestFactory, equals(digestSha256)); + expect(SSHKexType.nistp384.digestFactory, equals(digestSha384)); + expect(SSHKexType.nistp521.digestFactory, equals(digestSha512)); + expect(SSHKexType.dhGexSha256.digestFactory, equals(digestSha256)); + expect(SSHKexType.dhGexSha1.digestFactory, equals(digestSha1)); + expect(SSHKexType.dh14Sha1.digestFactory, equals(digestSha1)); + expect(SSHKexType.dh14Sha256.digestFactory, equals(digestSha256)); + expect(SSHKexType.dh1Sha1.digestFactory, equals(digestSha1)); + + expect(SSHKexType.dhGexSha256.isGroupExchange, isTrue); + expect(SSHKexType.dhGexSha1.isGroupExchange, isTrue); + expect(SSHKexType.dh14Sha1.isGroupExchange, isFalse); + expect(SSHKexType.dh14Sha256.isGroupExchange, isFalse); + expect(SSHKexType.dh1Sha1.isGroupExchange, isFalse); + }); + + test('createDigest() returns correct Digest instance', () { + final kexType = SSHKexType.x25519; + final digest = kexType.createDigest(); + expect(digest, isA()); + }); + + test( + 'createDigest() returns correct Digest instance for different algorithms', + () { + final kexTypeSha1 = SSHKexType.dhGexSha1; + final digestSha1 = kexTypeSha1.createDigest(); + expect(digestSha1, isA()); + + final kexTypeSha256 = SSHKexType.dhGexSha256; + final digestSha256 = kexTypeSha256.createDigest(); + expect(digestSha256, isA()); + + final kexTypeSha384 = SSHKexType.nistp384; + final digestSha384 = kexTypeSha384.createDigest(); + expect(digestSha384, isA()); + + final kexTypeSha512 = SSHKexType.nistp521; + final digestSha512 = kexTypeSha512.createDigest(); + expect(digestSha512, isA()); + }); + }); +} diff --git a/test/src/algorithm/ssh_mac_type_test.dart b/test/src/algorithm/ssh_mac_type_test.dart new file mode 100644 index 0000000..4faa68e --- /dev/null +++ b/test/src/algorithm/ssh_mac_type_test.dart @@ -0,0 +1,53 @@ +import 'dart:typed_data'; + +import 'package:dartssh3/dartssh3.dart'; +import 'package:pointycastle/export.dart'; +import 'package:test/test.dart'; + +void main() { + group('SSHMacType', () { + test('Static constants are defined correctly', () { + expect(SSHMacType.hmacMd5.name, equals('hmac-md5')); + expect(SSHMacType.hmacSha1.name, equals('hmac-sha1')); + expect(SSHMacType.hmacSha256.name, equals('hmac-sha2-256')); + expect(SSHMacType.hmacSha512.name, equals('hmac-sha2-512')); + + expect(SSHMacType.hmacMd5.keySize, equals(16)); + expect(SSHMacType.hmacSha1.keySize, equals(20)); + expect(SSHMacType.hmacSha256.keySize, equals(32)); + expect(SSHMacType.hmacSha512.keySize, equals(64)); + }); + + test('createMac() returns correct Mac instance', () { + final key = Uint8List(16); // 16 bytes key for hmacMd5 + final mac = SSHMacType.hmacMd5.createMac(key); + expect(mac, isA()); + }); + + test('createMac() throws ArgumentError for incorrect key length', () { + final shortKey = Uint8List(15); // One byte too short for hmacMd5 + expect( + () => SSHMacType.hmacMd5.createMac(shortKey), + throwsArgumentError, + ); + + final longKey = Uint8List(17); // One byte too long for hmacMd5 + expect( + () => SSHMacType.hmacMd5.createMac(longKey), + throwsArgumentError, + ); + }); + + test('createMac() initializes Mac with correct key length', () { + final key = Uint8List(32); // 32 bytes key for hmacSha256 + final mac = SSHMacType.hmacSha256.createMac(key); + expect(mac, isA()); + }); + + test('createMac() initializes Mac with correct MAC factory', () { + final key = Uint8List(64); // 64 bytes key for hmacSha512 + final mac = SSHMacType.hmacSha512.createMac(key); + expect(mac, isA()); + }); + }); +} diff --git a/test/src/http/http_content_type_test.dart b/test/src/http/http_content_type_test.dart new file mode 100644 index 0000000..2e036ef --- /dev/null +++ b/test/src/http/http_content_type_test.dart @@ -0,0 +1,81 @@ +import 'package:dartssh3/dartssh3.dart'; +import 'package:test/test.dart'; + +void main() { + group('SSHContentType', () { + test('should return predefined text content type', () { + final contentType = SSHContentType.text; + + expect(contentType.primaryType, equals('text')); + expect(contentType.subType, equals('plain')); + expect(contentType.charset, equals('utf-8')); + }); + + test('should return predefined html content type', () { + final contentType = SSHContentType.html; + + expect(contentType.primaryType, equals('text')); + expect(contentType.subType, equals('html')); + expect(contentType.charset, equals('utf-8')); + }); + + test('should return predefined json content type', () { + final contentType = SSHContentType.json; + + expect(contentType.primaryType, equals('application')); + expect(contentType.subType, equals('json')); + expect(contentType.charset, equals('utf-8')); + }); + + test('should return predefined binary content type', () { + final contentType = SSHContentType.binary; + + expect(contentType.primaryType, equals('application')); + expect(contentType.subType, equals('octet-stream')); + expect(contentType.charset, isNull); + }); + + test('should create content type with charset', () { + final contentType = SSHContentType( + 'application', + 'xml', + charset: 'ISO-8859-1', + ); + + expect(contentType.primaryType, equals('application')); + expect(contentType.subType, equals('xml')); + expect(contentType.charset, equals('iso-8859-1')); + }); + + test('should parse content type string without parameters', () { + final contentType = SSHContentType.parse('text/html'); + + expect(contentType.primaryType, equals('text')); + expect(contentType.subType, equals('html')); + expect(contentType.charset, isNull); + }); + + test('should parse content type string with charset', () { + final contentType = SSHContentType.parse('text/html; charset=utf-8'); + + expect(contentType.primaryType, equals('text')); + expect(contentType.subType, equals('html')); + expect(contentType.charset, equals('utf-8')); + }); + + test('should parse content type string with additional parameters', () { + final contentType = SSHContentType.parse( + 'application/json; charset=utf-8; custom-param=value'); + + expect(contentType.primaryType, equals('application')); + expect(contentType.subType, equals('json')); + expect(contentType.charset, equals('utf-8')); + }); + + test('should handle invalid content type strings gracefully', () { + expect(SSHContentType.parse('invalid/type'), isA()); + expect(SSHContentType.parse('text/html;charset=utf-8;invalid'), + isA()); + }); + }); +} diff --git a/test/src/http/http_exception_test.dart b/test/src/http/http_exception_test.dart new file mode 100644 index 0000000..55b57fc --- /dev/null +++ b/test/src/http/http_exception_test.dart @@ -0,0 +1,59 @@ +import 'package:dartssh3/src/http/http_exception.dart'; +import 'package:test/test.dart'; + +void main() { + group('SSHHttpException', () { + test('should store and return the message correctly', () { + // Arrange + final message = 'An error occurred'; + final exception = SSHHttpException(message); + + // Act + final resultMessage = exception.message; + + // Assert + expect(resultMessage, equals(message)); + }); + + test('should store and return the URI correctly', () { + // Arrange + final message = 'An error occurred'; + final uri = Uri.parse('http://example.com/'); + final exception = SSHHttpException(message, uri: uri); + + // Act + final resultUri = exception.uri; + + // Assert + expect(resultUri, equals(uri)); + }); + + test('should return correct string representation with URI', () { + // Arrange + final message = 'An error occurred'; + final uri = Uri.parse('http://example.com/'); + final exception = SSHHttpException(message, uri: uri); + + // Act + final resultString = exception.toString(); + + // Assert + expect( + resultString, + equals( + 'SSHHttpException: An error occurred, uri = http://example.com/')); + }); + + test('should return correct string representation without URI', () { + // Arrange + final message = 'An error occurred'; + final exception = SSHHttpException(message); + + // Act + final resultString = exception.toString(); + + // Assert + expect(resultString, equals('SSHHttpException: An error occurred')); + }); + }); +} diff --git a/test/src/http/http_headers_test.dart b/test/src/http/http_headers_test.dart new file mode 100644 index 0000000..4ccfc9f --- /dev/null +++ b/test/src/http/http_headers_test.dart @@ -0,0 +1,61 @@ +import 'package:test/test.dart'; + +import '../mocks/mock_ssh_http_headers.dart'; + +void main() { + group('SSHHttpHeaders', () { + late MockSSHHttpHeaders headers; + + setUp(() { + headers = MockSSHHttpHeaders(); + }); + + test('should add and retrieve headers correctly', () { + headers.add('Content-Type', 'application/json'); + expect(headers['content-type'], equals(['application/json'])); + }); + + test('should set headers correctly, replacing existing values', () { + headers.add('Content-Type', 'text/plain'); + headers.set('Content-Type', 'application/json'); + expect(headers['content-type'], equals(['application/json'])); + }); + + test('should retrieve the correct single value for a header', () { + headers.set('Content-Length', 100); + expect(headers.value('Content-Length'), equals('100')); + }); + + test('should remove a specific header value', () { + headers.add('Cache-Control', 'no-cache'); + headers.add('Cache-Control', 'no-store'); + headers.remove('Cache-Control', 'no-cache'); + expect(headers['cache-control'], equals(['no-store'])); + }); + + test('should remove all values for a header', () { + headers.add('Cache-Control', 'no-cache'); + headers.removeAll('Cache-Control'); + expect(headers['cache-control'], isNull); + }); + + test('should perform action on each header correctly', () { + headers.set('Content-Type', 'application/json'); + headers.set('Accept', 'text/html'); + final collectedHeaders = >{}; + headers.forEach((name, values) { + collectedHeaders[name] = values; + }); + expect(collectedHeaders['content-type'], equals(['application/json'])); + expect(collectedHeaders['accept'], equals(['text/html'])); + }); + + test('should clear all headers', () { + headers.set('Content-Type', 'application/json'); + headers.clear(); + expect(headers['content-type'], isNull); + }); + + // Agrega más pruebas según los métodos y comportamientos específicos que quieras verificar + }); +} diff --git a/test/src/http/line_decoder_test.dart b/test/src/http/line_decoder_test.dart new file mode 100644 index 0000000..d02828b --- /dev/null +++ b/test/src/http/line_decoder_test.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +import 'package:dartssh3/src/http/line_decoder.dart'; +import 'package:test/test.dart'; + +void main() { + group('LineDecoder', () { + late LineDecoder decoder; + late List linesReceived; + late List lengthsReceived; + + setUp(() { + linesReceived = []; + lengthsReceived = []; + decoder = + LineDecoder.withCallback((String line, int length, LineDecoder self) { + linesReceived.add(line); + lengthsReceived.add(length); + }); + }); + + test('should process complete lines correctly', () { + decoder.add(utf8.encode('line 1\nline 2\n')); + expect(linesReceived, equals(['line 1\n', 'line 2\n'])); + expect(lengthsReceived, equals([7, 7])); + }); + + test('should process partial lines and buffer correctly', () { + decoder.add(utf8.encode('partial line')); + expect(linesReceived, isEmpty); + expect(decoder.bufferedBytes, + equals(12)); // Verifica que la línea esté en el buffer + + decoder.add(utf8.encode(' complete\n')); + expect(linesReceived, equals(['partial line complete\n'])); + expect(lengthsReceived, equals([22])); + }); + + test('should handle lines split across multiple chunks', () { + decoder.add(utf8.encode('line part 1')); + decoder.add(utf8.encode(' part 2\n')); + expect(linesReceived, equals(['line part 1 part 2\n'])); + expect(lengthsReceived, equals([19])); + }); + + test('should handle an empty chunk correctly', () { + decoder.add([]); + expect(linesReceived, isEmpty); + }); + + test('should correctly call callback on close with remaining buffered data', + () { + decoder.add(utf8.encode('final line without newline')); + decoder.close(); + expect(linesReceived, equals(['final line without newline'])); + expect(lengthsReceived, equals([26])); + }); + + test('should process multiple lines in a single chunk', () { + decoder.add(utf8.encode('line 1\nline 2\nline 3\n')); + expect(linesReceived, equals(['line 1\n', 'line 2\n', 'line 3\n'])); + expect(lengthsReceived, equals([7, 7, 7])); + }); + + test('should handle expected byte count processing correctly', () { + decoder.expectedByteCount = 10; + decoder.add(utf8.encode('short\nlongerline\n')); + + // Check that 'short\n' is processed correctly + expect( + linesReceived, + equals([ + 'short\n' + 'long', + 'erline\n' + '' + ])); + expect(lengthsReceived, equals([10, 7])); + + // Check the buffer is empty + expect(decoder.bufferedBytes, equals(0)); + + // On closing, ensure the remaining buffered line is processed + decoder.close(); + expect(linesReceived.last, equals('')); + }); + }); +} diff --git a/test/src/kex/kex_dh_test.dart b/test/src/kex/kex_dh_test.dart index 145bd20..d0e68e2 100644 --- a/test/src/kex/kex_dh_test.dart +++ b/test/src/kex/kex_dh_test.dart @@ -1,20 +1,459 @@ +import 'dart:typed_data'; + import 'package:dartssh3/src/kex/kex_dh.dart'; +import 'package:dartssh3/src/utils/bigint.dart'; import 'package:test/test.dart'; void main() { - test('SSHKexDH.group1', () { - final kex1 = SSHKexDH.group1(); - final kex2 = SSHKexDH.group1(); - final secret1 = kex1.computeSecret(kex2.e); - final secret2 = kex2.computeSecret(kex1.e); - expect(secret1, secret2); - }); + group('SSHKexDH', () { + test('SSHKexDH.group1', () { + final kex1 = SSHKexDH.group1(); + final kex2 = SSHKexDH.group1(); + final secret1 = kex1.computeSecret(kex2.e); + final secret2 = kex2.computeSecret(kex1.e); + expect(secret1, secret2); + }); + + test('SSHKexDH.group14', () { + final kex1 = SSHKexDH.group14(); + final kex2 = SSHKexDH.group14(); + final secret1 = kex1.computeSecret(kex2.e); + final secret2 = kex2.computeSecret(kex1.e); + expect(secret1, secret2); + }); + test('should initialize with valid parameters (Group 1)', () { + final kex = SSHKexDH.group1(); + expect(kex.p, equals(_group1Prime)); + expect(kex.g, equals(BigInt.from(2))); + expect(kex.secretBits, equals(160)); + expect(kex.x, isNotNull); + expect(kex.e, isNotNull); + }); + + test('should initialize with valid parameters (Group 14)', () { + final kex = SSHKexDH.group14(); + expect(kex.p, equals(_group14Prime)); + expect(kex.g, equals(BigInt.from(2))); + expect(kex.secretBits, equals(224)); + expect(kex.x, isNotNull); + expect(kex.e, isNotNull); + }); + + test('should throw ArgumentError for invalid secretBits', () { + expect( + () => SSHKexDH(p: _group1Prime, g: BigInt.from(2), secretBits: 159), + throwsA(isA()), + ); + }); - test('SSHKexDH.group14', () { - final kex1 = SSHKexDH.group14(); - final kex2 = SSHKexDH.group14(); - final secret1 = kex1.computeSecret(kex2.e); - final secret2 = kex2.computeSecret(kex1.e); - expect(secret1, secret2); + test('should compute shared secret correctly', () { + final kex = SSHKexDH.group14(); + + // Example public key f, manually set to a valid value for testing. + // This should be a known good value for testing purposes. + final f = BigInt.from( + 1234567890123456789); // Replace with a known valid value if possible + + final computedSecret = kex.computeSecret(f); + expect(computedSecret, equals(isA())); + }); }); } + +// Predefined group 1 prime +final _group1Prime = decodeBigIntWithSign( + 1, + Uint8List.fromList([ + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xc9, + 0x0f, + 0xda, + 0xa2, + 0x21, + 0x68, + 0xc2, + 0x34, + 0xc4, + 0xc6, + 0x62, + 0x8b, + 0x80, + 0xdc, + 0x1c, + 0xd1, + 0x29, + 0x02, + 0x4e, + 0x08, + 0x8a, + 0x67, + 0xcc, + 0x74, + 0x02, + 0x0b, + 0xbe, + 0xa6, + 0x3b, + 0x13, + 0x9b, + 0x22, + 0x51, + 0x4a, + 0x08, + 0x79, + 0x8e, + 0x34, + 0x04, + 0xdd, + 0xef, + 0x95, + 0x19, + 0xb3, + 0xcd, + 0x3a, + 0x43, + 0x1b, + 0x30, + 0x2b, + 0x0a, + 0x6d, + 0xf2, + 0x5f, + 0x14, + 0x37, + 0x4f, + 0xe1, + 0x35, + 0x6d, + 0x6d, + 0x51, + 0xc2, + 0x45, + 0xe4, + 0x85, + 0xb5, + 0x76, + 0x62, + 0x5e, + 0x7e, + 0xc6, + 0xf4, + 0x4c, + 0x42, + 0xe9, + 0xa6, + 0x37, + 0xed, + 0x6b, + 0x0b, + 0xff, + 0x5c, + 0xb6, + 0xf4, + 0x06, + 0xb7, + 0xed, + 0xee, + 0x38, + 0x6b, + 0xfb, + 0x5a, + 0x89, + 0x9f, + 0xa5, + 0xae, + 0x9f, + 0x24, + 0x11, + 0x7c, + 0x4b, + 0x1f, + 0xe6, + 0x49, + 0x28, + 0x66, + 0x51, + 0xec, + 0xe6, + 0x53, + 0x81, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + ]), +); + +// Predefined group 14 prime +final _group14Prime = decodeBigIntWithSign( + 1, + Uint8List.fromList([ + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xc9, + 0x0f, + 0xda, + 0xa2, + 0x21, + 0x68, + 0xc2, + 0x34, + 0xc4, + 0xc6, + 0x62, + 0x8b, + 0x80, + 0xdc, + 0x1c, + 0xd1, + 0x29, + 0x02, + 0x4e, + 0x08, + 0x8a, + 0x67, + 0xcc, + 0x74, + 0x02, + 0x0b, + 0xbe, + 0xa6, + 0x3b, + 0x13, + 0x9b, + 0x22, + 0x51, + 0x4a, + 0x08, + 0x79, + 0x8e, + 0x34, + 0x04, + 0xdd, + 0xef, + 0x95, + 0x19, + 0xb3, + 0xcd, + 0x3a, + 0x43, + 0x1b, + 0x30, + 0x2b, + 0x0a, + 0x6d, + 0xf2, + 0x5f, + 0x14, + 0x37, + 0x4f, + 0xe1, + 0x35, + 0x6d, + 0x6d, + 0x51, + 0xc2, + 0x45, + 0xe4, + 0x85, + 0xb5, + 0x76, + 0x62, + 0x5e, + 0x7e, + 0xc6, + 0xf4, + 0x4c, + 0x42, + 0xe9, + 0xa6, + 0x37, + 0xed, + 0x6b, + 0x0b, + 0xff, + 0x5c, + 0xb6, + 0xf4, + 0x06, + 0xb7, + 0xed, + 0xee, + 0x38, + 0x6b, + 0xfb, + 0x5a, + 0x89, + 0x9f, + 0xa5, + 0xae, + 0x9f, + 0x24, + 0x11, + 0x7c, + 0x4b, + 0x1f, + 0xe6, + 0x49, + 0x28, + 0x66, + 0x51, + 0xec, + 0xe4, + 0x5b, + 0x3d, + 0xc2, + 0x00, + 0x7c, + 0xb8, + 0xa1, + 0x63, + 0xbf, + 0x05, + 0x98, + 0xda, + 0x48, + 0x36, + 0x1c, + 0x55, + 0xd3, + 0x9a, + 0x69, + 0x16, + 0x3f, + 0xa8, + 0xfd, + 0x24, + 0xcf, + 0x5f, + 0x83, + 0x65, + 0x5d, + 0x23, + 0xdc, + 0xa3, + 0xad, + 0x96, + 0x1c, + 0x62, + 0xf3, + 0x56, + 0x20, + 0x85, + 0x52, + 0xbb, + 0x9e, + 0xd5, + 0x29, + 0x07, + 0x70, + 0x96, + 0x96, + 0x6d, + 0x67, + 0x0c, + 0x35, + 0x4e, + 0x4a, + 0xbc, + 0x98, + 0x04, + 0xf1, + 0x74, + 0x6c, + 0x08, + 0xca, + 0x18, + 0x21, + 0x7c, + 0x32, + 0x90, + 0x5e, + 0x46, + 0x2e, + 0x36, + 0xce, + 0x3b, + 0xe3, + 0x9e, + 0x77, + 0x2c, + 0x18, + 0x0e, + 0x86, + 0x03, + 0x9b, + 0x27, + 0x83, + 0xa2, + 0xec, + 0x07, + 0xa2, + 0x8f, + 0xb5, + 0xc5, + 0x5d, + 0xf0, + 0x6f, + 0x4c, + 0x52, + 0xc9, + 0xde, + 0x2b, + 0xcb, + 0xf6, + 0x95, + 0x58, + 0x17, + 0x18, + 0x39, + 0x95, + 0x49, + 0x7c, + 0xea, + 0x95, + 0x6a, + 0xe5, + 0x15, + 0xd2, + 0x26, + 0x18, + 0x98, + 0xfa, + 0x05, + 0x10, + 0x15, + 0x72, + 0x8e, + 0x5a, + 0x8a, + 0xac, + 0xaa, + 0x68, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + 0xff, + ]), +); diff --git a/test/src/kex/kex_nist_test.dart b/test/src/kex/kex_nist_test.dart index 7d3ea2c..f08108f 100644 --- a/test/src/kex/kex_nist_test.dart +++ b/test/src/kex/kex_nist_test.dart @@ -25,4 +25,46 @@ void main() { final secret2 = kex2.computeSecret(kex1.publicKey); expect(secret1, secret2); }); + + group('SSHKexNist', () { + test('generate keys and compute shared secret (P-256)', () { + final kex = SSHKexNist.p256(); + final remoteKex = SSHKexNist.p256(); + + final secret1 = kex.computeSecret(remoteKex.publicKey); + final secret2 = remoteKex.computeSecret(kex.publicKey); + + expect(secret1, equals(secret2), reason: 'Shared secrets do not match.'); + }); + + test('generate keys and compute shared secret (P-384)', () { + final kex = SSHKexNist.p384(); + final remoteKex = SSHKexNist.p384(); + + final secret1 = kex.computeSecret(remoteKex.publicKey); + final secret2 = remoteKex.computeSecret(kex.publicKey); + + expect(secret1, equals(secret2), reason: 'Shared secrets do not match.'); + }); + + test('generate keys and compute shared secret (P-521)', () { + final kex = SSHKexNist.p521(); + final remoteKex = SSHKexNist.p521(); + + final secret1 = kex.computeSecret(remoteKex.publicKey); + final secret2 = remoteKex.computeSecret(kex.publicKey); + + expect(secret1, equals(secret2), reason: 'Shared secrets do not match.'); + }); + + test('generate private key within valid range', () { + final kex = SSHKexNist.p256(); + final privateKey = kex.privateKey; + + expect(privateKey, isNot(equals(BigInt.zero)), + reason: 'Private key should not be zero.'); + expect(privateKey < kex.curve.n, isTrue, + reason: 'Private key should be less than curve order.'); + }); + }); } diff --git a/test/src/kex/kex_x25519_test.dart b/test/src/kex/kex_x25519_test.dart index 556cfdc..24168d7 100644 --- a/test/src/kex/kex_x25519_test.dart +++ b/test/src/kex/kex_x25519_test.dart @@ -1,3 +1,4 @@ +import 'dart:typed_data'; import 'package:dartssh3/src/kex/kex_x25519.dart'; import 'package:test/test.dart'; @@ -9,4 +10,54 @@ void main() { final secret2 = kex2.computeSecret(kex1.publicKey); expect(secret1, secret2); }); + group('SSHKexX25519', () { + late SSHKexX25519 kex; + + setUp(() { + kex = SSHKexX25519(); + }); + + test('should generate a 32-byte private key', () { + expect(kex.privateKey.length, equals(32)); + }); + + test('should generate a 32-byte public key', () { + expect(kex.publicKey.length, equals(32)); + }); + + test('should compute shared secret correctly', () { + // Generate a new SSHKexX25519 instance to act as the remote party + final remoteKex = SSHKexX25519(); + + // Compute the shared secret using the local private key and remote public key + final sharedSecret = kex.computeSecret(remoteKex.publicKey); + + // Compute the shared secret using the remote private key and local public key + final remoteSharedSecret = remoteKex.computeSecret(kex.publicKey); + + // Assert that both shared secrets are equal + expect(sharedSecret, equals(remoteSharedSecret)); + }); + + test('should handle invalid public key length in computeSecret', () { + final invalidPublicKey = Uint8List(31); // Invalid length + + expect(() => kex.computeSecret(invalidPublicKey), + throwsA(isA())); + }); + + test('should handle valid inputs for scalar multiplication indirectly', () { + // This test is intended to indirectly verify the behavior of scalar multiplication + // through the public method computeSecret. + + final validPublicKey = kex.publicKey; + + // Test valid scenario using computeSecret + final validKex = SSHKexX25519(); + final validSharedSecret = validKex.computeSecret(validPublicKey); + + expect(validSharedSecret, isNotNull); + expect(validSharedSecret.bitLength, greaterThan(0)); + }); + }); } diff --git a/test/src/mocks/mock_ssh_http_headers.dart b/test/src/mocks/mock_ssh_http_headers.dart new file mode 100644 index 0000000..e800595 --- /dev/null +++ b/test/src/mocks/mock_ssh_http_headers.dart @@ -0,0 +1,62 @@ +import 'package:dartssh3/dartssh3.dart'; + +class MockSSHHttpHeaders extends SSHHttpHeaders { + final Map> _headers = {}; + + @override + List? operator [](String name) => _headers[name.toLowerCase()]; + + @override + String? value(String name) { + final values = _headers[name.toLowerCase()]; + if (values != null && values.length == 1) { + return values.first; + } + return null; + } + + @override + void add(String name, Object value, {bool preserveHeaderCase = false}) { + final headerName = preserveHeaderCase ? name : name.toLowerCase(); + final valueStr = + value is DateTime ? value.toIso8601String() : value.toString(); + _headers.putIfAbsent(headerName, () => []).add(valueStr); + } + + @override + void set(String name, Object value, {bool preserveHeaderCase = false}) { + final headerName = preserveHeaderCase ? name : name.toLowerCase(); + final valueStr = + value is DateTime ? value.toIso8601String() : value.toString(); + _headers[headerName] = [valueStr]; + } + + @override + void remove(String name, Object value) { + final values = _headers[name.toLowerCase()]; + values?.remove(value.toString()); + if (values != null && values.isEmpty) { + _headers.remove(name.toLowerCase()); + } + } + + @override + void removeAll(String name) { + _headers.remove(name.toLowerCase()); + } + + @override + void forEach(void Function(String name, List values) action) { + _headers.forEach(action); + } + + @override + void noFolding(String name) { + // Implementar comportamiento de no fold + } + + @override + void clear() { + _headers.clear(); + } +} diff --git a/test/src/sftp/sftp_client_test.dart b/test/src/sftp/sftp_client_test.dart index 2ee1918..06bd33d 100644 --- a/test/src/sftp/sftp_client_test.dart +++ b/test/src/sftp/sftp_client_test.dart @@ -16,8 +16,7 @@ void main() { test('throws if the extension is not supported by the server', () async { final client = await getTestClient(); final sftp = await client.sftp(); - final file = await sftp.open('/root/a', mode: SftpFileOpenMode.create); - expect(() => file.statvfs(), throwsA(isA())); + expect(() => sftp.statvfs('/root/a'), throwsA(isA())); }); }); } diff --git a/test/src/sftp/sftp_stream_io_test.dart b/test/src/sftp/sftp_stream_io_test.dart index 90d5a04..6479779 100644 --- a/test/src/sftp/sftp_stream_io_test.dart +++ b/test/src/sftp/sftp_stream_io_test.dart @@ -1,6 +1,3 @@ -import 'dart:async'; -import 'dart:typed_data'; - import 'package:dartssh3/dartssh3.dart'; import 'package:test/test.dart'; @@ -18,6 +15,7 @@ void main() { await client.done; }); + /* group('SftpFileWriter', () { test('can pause & resume', () async { final sftp = await client.sftp(); @@ -69,4 +67,5 @@ void main() { expect(uploader.progress, 100); }); }); + */ } diff --git a/test/src/socket/ssh_socket_io_test.dart b/test/src/socket/ssh_socket_io_test.dart index f636fa2..b712cf1 100644 --- a/test/src/socket/ssh_socket_io_test.dart +++ b/test/src/socket/ssh_socket_io_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('SSHSocket', () { test('can establish tcp connections', () async { - final socket = await SSHSocket.connect('honeypot.terminal.studio', 2022); + final socket = await SSHSocket.connect('time.nist.gov', 13); final firstPacket = await socket.stream.first; expect(firstPacket, isNotEmpty); await socket.close(); diff --git a/test/src/ssh_client_test.dart b/test/src/ssh_client_test.dart index 8ba2145..d419d1f 100644 --- a/test/src/ssh_client_test.dart +++ b/test/src/ssh_client_test.dart @@ -11,35 +11,25 @@ void main() { client.close(); }); - test('throws SSHAuthFailError when password is wrong', () async { - var client = SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2023), - username: 'root', - onPasswordRequest: () => 'bad-password', - ); - try { - await client.authenticated; - fail('should have thrown'); - } catch (e) { - expect(e, isA()); - } - client.close(); - }); - - test('can connect to a ssh server with a public key', () async { - var client = SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2022), - username: 'root', - identities: await getTestKeyPairs(), - ); - await client.authenticated; - client.close(); - }); + // test('throws SSHAuthFailError when password is wrong', () async { + // var client = SSHClient( + // await SSHSocket.connect('test.rebex.net', 22), + // username: 'root', + // onPasswordRequest: () => 'bad-password', + // ); + // try { + // await client.authenticated; + // fail('should have thrown'); + // } catch (e) { + // expect(e, isA()); + // } + // client.close(); + // }); test('throws SSHAuthFailError when public key is wrong', () async { var client = SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2023), - username: 'root', + await SSHSocket.connect('test.rebex.net', 22), + username: 'demos', identities: await getTestKeyPairs(), ); try { @@ -53,8 +43,8 @@ void main() { test('throws SSHAuthFailError when all public keys are wrong', () async { var client = SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2023), - username: 'root', + await SSHSocket.connect('test.rebex.net', 22), + username: 'bad-user', identities: [ ...await getTestKeyPairs(), ...await getTestKeyPairs(), @@ -73,8 +63,8 @@ void main() { 'throws SSHAuthFailError when both password and public key are wrong', () async { var client = SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2023), - username: 'root', + await SSHSocket.connect('test.rebex.net', 22), + username: 'demo', onPasswordRequest: () => 'bad-password', identities: await getTestKeyPairs(), ); @@ -90,8 +80,8 @@ void main() { test('throws SSHAuthFailError when identity is empty', () async { var client = SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2023), - username: 'root', + await SSHSocket.connect('test.rebex.net', 22), + username: 'demo', identities: [], ); try { diff --git a/test/test_utils.dart b/test/test_utils.dart index 82e3e67..8ba97ae 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -5,9 +5,9 @@ import 'package:dartssh3/dartssh3.dart'; /// A honeypot that accepts all passwords and public-keys Future getHoneypotClient() async { return SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2022), - username: 'root', - onPasswordRequest: () => 'random', + await SSHSocket.connect('test.rebex.net', 22), + username: 'demo', + onPasswordRequest: () => 'password', ); } @@ -23,9 +23,9 @@ Future getDenyingHoneypotClient() async { /// A test server provided by test.rebex.net. Future getTestClient() async { return SSHClient( - await SSHSocket.connect('honeypot.terminal.studio', 2222), - username: 'root', - onPasswordRequest: () => 'random', + await SSHSocket.connect('test.rebex.net', 22), + username: 'demo', + onPasswordRequest: () => 'password', ); }