├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .classpath ├── .gitignore ├── .project ├── .settings │ └── org.eclipse.buildship.core.prefs ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── sq │ └── flutter │ └── ssh │ └── SshPlugin.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── .project │ ├── .settings │ │ └── org.eclipse.buildship.core.prefs │ ├── app │ │ ├── .classpath │ │ ├── .project │ │ ├── .settings │ │ │ └── org.eclipse.buildship.core.prefs │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── sq │ │ │ │ └── flutter │ │ │ │ └── sshexample │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ └── main.dart ├── pubspec.yaml ├── ssh_example.iml ├── ssh_example_android.iml └── test │ └── widget_test.dart ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── SSHClient.h │ ├── SSHClient.m │ ├── SshPlugin.h │ └── SshPlugin.m └── ssh.podspec ├── lib └── ssh.dart ├── pubspec.yaml ├── ssh.iml └── ssh_android.iml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | pubspec.lock 7 | 8 | build/ 9 | 10 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.7 2 | 3 | * Moved getInputStream before channel connect on Android. 4 | * Set minimum SDK version to 2.1.0. 5 | 6 | ## 0.0.6 7 | 8 | * Set compileSdkVersion to 28, fixing build error "Execution failed for task ':ssh:verifyReleaseResources'" 9 | * Set uuid plugin version to ^2.0.0. 10 | 11 | ## 0.0.5 12 | 13 | * Fixed download exception on Android due to [this issue](https://github.com/flutter/flutter/issues/34993). 14 | 15 | ## 0.0.4 16 | 17 | * Changed disconnect() method to sync. 18 | * Fixed Android crash issue "Methods marked with @UiThread must be executed on the main thread.". 19 | 20 | ## 0.0.3 21 | 22 | * Fixed invokeMethod missing indirection. 23 | * Add note about OpenSSH keys, add passphrase to key example. 24 | 25 | ## 0.0.2 26 | 27 | * Check if client is null before getting session in Android. 28 | 29 | ## 0.0.1 30 | 31 | * Initial release. 32 | 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Qian Sha 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh 2 | 3 | SSH and SFTP client for Flutter. Wraps iOS library [NMSSH](https://github.com/NMSSH/NMSSH) and Android library [JSch](http://www.jcraft.com/jsch/). 4 | 5 | ## Installation 6 | 7 | Add `ssh` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). 8 | 9 | ## Known issue 10 | 11 | - Platform exception in release mode for Android: 12 | 13 | ``` 14 | PlatformException(connection_failure, java.lang.ClassNotFoundException: com.jcraft.jsch.jce.Random, null) 15 | ``` 16 | 17 | There are 2 workarounds: 18 | 19 | - Disable shrink: 20 | 21 | `flutter build apk --no-shrink` 22 | 23 | - Configure proguard-rules. Refer to [this comment](https://github.com/shaqian/flutter_ssh/issues/27#issuecomment-599180850) for details. 24 | 25 | ## Usage 26 | 27 | ### Create a client using password authentication 28 | ```dart 29 | import 'package:ssh/ssh.dart'; 30 | 31 | var client = new SSHClient( 32 | host: "my.sshtest", 33 | port: 22, 34 | username: "sha", 35 | passwordOrKey: "Password01.", 36 | ); 37 | ``` 38 | 39 | ### Create a client using public key authentication 40 | ```dart 41 | import 'package:ssh/ssh.dart'; 42 | 43 | var client = new SSHClient( 44 | host: "my.sshtest", 45 | port: 22, 46 | username: "sha", 47 | passwordOrKey: { 48 | "privateKey": """-----BEGIN RSA PRIVATE KEY----- 49 | ...... 50 | -----END RSA PRIVATE KEY-----""", 51 | "passphrase": "passphrase-for-key", 52 | }, 53 | ); 54 | ``` 55 | 56 | #### OpenSSH keys 57 | 58 | Recent versions of OpenSSH introduce a proprietary key format that is not supported by most other software, including this one, you must convert it to a PEM-format RSA key using the `puttygen` tool. On Windows this is a graphical tool. On the Mac, install it per [these instructions](https://www.ssh.com/ssh/putty/mac/). On Linux install your distribution's `putty` or `puttygen` packages. 59 | 60 | * Temporarily remove the passphrase from the original key (enter blank password as the new password) 61 | `ssh-keygen -p -f id_rsa` 62 | * convert to RSA PEM format 63 | `puttygen id_rsa -O private-openssh -o id_rsa_unencrypted` 64 | * re-apply the passphrase to the original key 65 | `ssh-keygen -p -f id_rsa` 66 | * apply a passphrase to the converted key: 67 | `puttygen id_rsa_unencrypted -P -O private-openssh -o id_rsa_flutter` 68 | * remove the unencrypted version: 69 | `rm id_rsa_unencrypted` 70 | 71 | ### Connect client 72 | ```dart 73 | await client.connect(); 74 | ``` 75 | 76 | ### Close client 77 | ```dart 78 | await client.disconnect(); 79 | ``` 80 | 81 | ### Execute SSH command 82 | ```dart 83 | var result = await client.execute("ps"); 84 | ``` 85 | 86 | ### Shell 87 | 88 | #### Start shell: 89 | - Supported ptyType: vanilla, vt100, vt102, vt220, ansi, xterm 90 | ```dart 91 | var result = await client.startShell( 92 | ptyType: "xterm", // defaults to vanilla 93 | callback: (dynamic res) { 94 | print(res); // read from shell 95 | } 96 | ); 97 | ``` 98 | 99 | #### Write to shell: 100 | ```dart 101 | await client.writeToShell("ls\n"); 102 | ``` 103 | 104 | #### Close shell: 105 | ```dart 106 | await client.closeShell(); 107 | ``` 108 | 109 | ### SFTP 110 | 111 | #### Connect SFTP: 112 | ```dart 113 | await client.connectSFTP(); 114 | ``` 115 | 116 | #### List directory: 117 | ```dart 118 | var array = await client.sftpLs("/home"); // defaults to . 119 | ``` 120 | 121 | #### Create directory: 122 | ```dart 123 | await client.sftpMkdir("testdir"); 124 | ``` 125 | 126 | #### Rename file or directory: 127 | ```dart 128 | await client.sftpRename( 129 | oldPath: "testfile", 130 | newPath: "newtestfile", 131 | ); 132 | ``` 133 | 134 | #### Remove directory: 135 | ```dart 136 | await client.sftpRmdir("testdir"); 137 | ``` 138 | 139 | #### Remove file: 140 | ```dart 141 | await client.sftpRm("testfile"); 142 | ``` 143 | 144 | #### Download file: 145 | ```dart 146 | var filePath = await client.sftpDownload( 147 | path: "testfile", 148 | toPath: tempPath, 149 | callback: (progress) { 150 | print(progress); // read download progress 151 | }, 152 | ); 153 | 154 | // Cancel download: 155 | await client.sftpCancelDownload(); 156 | ``` 157 | 158 | #### Upload file: 159 | ```dart 160 | await client.sftpUpload( 161 | path: filePath, 162 | toPath: ".", 163 | callback: (progress) { 164 | print(progress); // read upload progress 165 | }, 166 | ); 167 | 168 | // Cancel upload: 169 | await client.sftpCancelUpload(); 170 | ``` 171 | 172 | #### Close SFTP: 173 | ```dart 174 | await client.disconnectSFTP(); 175 | ``` 176 | 177 | ## Demo 178 | 179 | Refer to the [example](https://github.com/shaqian/flutter_ssh/tree/master/example). 180 | -------------------------------------------------------------------------------- /android/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | ssh 4 | Project ssh created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=../example/android 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'sq.flutter.ssh' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.1.2' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | 35 | dependencies { 36 | implementation 'com.jcraft:jsch:0.1.54' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ssh' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/sq/flutter/ssh/SshPlugin.java: -------------------------------------------------------------------------------- 1 | package sq.flutter.ssh; 2 | 3 | import android.util.Log; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | 7 | import com.jcraft.jsch.Channel; 8 | import com.jcraft.jsch.ChannelExec; 9 | import com.jcraft.jsch.ChannelSftp; 10 | import com.jcraft.jsch.ChannelSftp.LsEntry; 11 | import com.jcraft.jsch.ChannelShell; 12 | import com.jcraft.jsch.JSch; 13 | import com.jcraft.jsch.JSchException; 14 | import com.jcraft.jsch.Session; 15 | import com.jcraft.jsch.SftpException; 16 | import com.jcraft.jsch.SftpProgressMonitor; 17 | 18 | import java.io.BufferedReader; 19 | import java.io.DataOutputStream; 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.io.InputStreamReader; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.Properties; 29 | import java.util.Vector; 30 | import java.text.SimpleDateFormat; 31 | import java.util.Date; 32 | 33 | import io.flutter.plugin.common.EventChannel; 34 | import io.flutter.plugin.common.EventChannel.EventSink; 35 | import io.flutter.plugin.common.EventChannel.StreamHandler; 36 | import io.flutter.plugin.common.MethodCall; 37 | import io.flutter.plugin.common.MethodChannel; 38 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 39 | import io.flutter.plugin.common.MethodChannel.Result; 40 | import io.flutter.plugin.common.PluginRegistry.Registrar; 41 | 42 | /** SshPlugin */ 43 | public class SshPlugin implements MethodCallHandler, StreamHandler { 44 | /** Plugin registration. */ 45 | public static void registerWith(Registrar registrar) { 46 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "ssh"); 47 | final EventChannel eventChannel = new EventChannel(registrar.messenger(), "shell_sftp"); 48 | final SshPlugin instance = new SshPlugin(); 49 | channel.setMethodCallHandler(instance); 50 | eventChannel.setStreamHandler(instance); 51 | } 52 | 53 | // MethodChannel.Result wrapper that responds on the platform thread. 54 | private static class MethodResultWrapper implements Result { 55 | private Result methodResult; 56 | private Handler handler; 57 | 58 | MethodResultWrapper(Result result) { 59 | methodResult = result; 60 | handler = new Handler(Looper.getMainLooper()); 61 | } 62 | 63 | @Override 64 | public void success(final Object result) { 65 | handler.post( 66 | new Runnable() { 67 | @Override 68 | public void run() { 69 | methodResult.success(result); 70 | } 71 | }); 72 | } 73 | 74 | @Override 75 | public void error( 76 | final String errorCode, final String errorMessage, final Object errorDetails) { 77 | handler.post( 78 | new Runnable() { 79 | @Override 80 | public void run() { 81 | methodResult.error(errorCode, errorMessage, errorDetails); 82 | } 83 | }); 84 | } 85 | 86 | @Override 87 | public void notImplemented() { 88 | handler.post( 89 | new Runnable() { 90 | @Override 91 | public void run() { 92 | methodResult.notImplemented(); 93 | } 94 | }); 95 | } 96 | } 97 | 98 | private static class MainThreadEventSink implements EventChannel.EventSink { 99 | private EventChannel.EventSink eventSink; 100 | private Handler handler; 101 | 102 | MainThreadEventSink(EventChannel.EventSink eventSink) { 103 | this.eventSink = eventSink; 104 | handler = new Handler(Looper.getMainLooper()); 105 | } 106 | 107 | @Override 108 | public void success(final Object o) { 109 | handler.post(new Runnable() { 110 | @Override 111 | public void run() { 112 | eventSink.success(o); 113 | } 114 | }); 115 | } 116 | 117 | @Override 118 | public void error(final String s, final String s1, final Object o) { 119 | handler.post(new Runnable() { 120 | @Override 121 | public void run() { 122 | eventSink.error(s, s1, o); 123 | } 124 | }); 125 | } 126 | 127 | @Override 128 | public void endOfStream() { 129 | handler.post(new Runnable() { 130 | @Override 131 | public void run() { 132 | eventSink.endOfStream(); 133 | } 134 | }); 135 | } 136 | } 137 | 138 | @Override 139 | public void onMethodCall(MethodCall call, Result rawResult) { 140 | Result result = new MethodResultWrapper(rawResult); 141 | if (call.method.equals("connectToHost")) { 142 | connectToHost((HashMap) call.arguments, result); 143 | } else if (call.method.equals("execute")) { 144 | execute((HashMap) call.arguments, result); 145 | } else if (call.method.equals("portForwardL")) { 146 | portForwardL((HashMap) call.arguments, result); 147 | } else if (call.method.equals("startShell")) { 148 | startShell((HashMap) call.arguments, result); 149 | } else if (call.method.equals("writeToShell")) { 150 | writeToShell((HashMap) call.arguments, result); 151 | } else if (call.method.equals("closeShell")) { 152 | closeShell((HashMap) call.arguments); 153 | } else if (call.method.equals("connectSFTP")) { 154 | connectSFTP((HashMap) call.arguments, result); 155 | } else if (call.method.equals("sftpLs")) { 156 | sftpLs((HashMap) call.arguments, result); 157 | } else if (call.method.equals("sftpRename")) { 158 | sftpRename((HashMap) call.arguments, result); 159 | } else if (call.method.equals("sftpMkdir")) { 160 | sftpMkdir((HashMap) call.arguments, result); 161 | } else if (call.method.equals("sftpRm")) { 162 | sftpRm((HashMap) call.arguments, result); 163 | } else if (call.method.equals("sftpRmdir")) { 164 | sftpRmdir((HashMap) call.arguments, result); 165 | } else if (call.method.equals("sftpDownload")) { 166 | sftpDownload((HashMap) call.arguments, result); 167 | } else if (call.method.equals("sftpUpload")) { 168 | sftpUpload((HashMap) call.arguments, result); 169 | } else if (call.method.equals("sftpCancelDownload")) { 170 | sftpCancelDownload((HashMap) call.arguments); 171 | } else if (call.method.equals("sftpCancelUpload")) { 172 | sftpCancelUpload((HashMap) call.arguments); 173 | } else if (call.method.equals("disconnectSFTP")) { 174 | disconnectSFTP((HashMap) call.arguments); 175 | } else if (call.method.equals("disconnect")) { 176 | disconnect((HashMap) call.arguments); 177 | } else if (call.method.equals("isConnected")) { 178 | isConnected((HashMap) call.arguments, result); 179 | } else { 180 | result.notImplemented(); 181 | } 182 | } 183 | 184 | private class SSHClient { 185 | Session _session; 186 | String _key; 187 | BufferedReader _bufferedReader; 188 | DataOutputStream _dataOutputStream; 189 | Channel _channel = null; 190 | ChannelSftp _sftpSession = null; 191 | Boolean _downloadContinue = false; 192 | Boolean _uploadContinue = false; 193 | } 194 | 195 | private static final String LOGTAG = "SshPlugin"; 196 | 197 | Map clientPool = new HashMap<>(); 198 | private EventSink eventSink; 199 | 200 | private SSHClient getClient(final String key, final Result result) { 201 | SSHClient client = clientPool.get(key); 202 | if (client == null) 203 | result.error("unknown_client", "Unknown client", null); 204 | return client; 205 | } 206 | 207 | @Override 208 | public void onListen(Object arguments, EventSink events) { 209 | this.eventSink = new MainThreadEventSink(events); 210 | } 211 | 212 | @Override 213 | public void onCancel(Object arguments) { 214 | this.eventSink = null; 215 | } 216 | 217 | private void connectToHost(final HashMap args, final Result result) { 218 | new Thread(new Runnable() { 219 | public void run() { 220 | try { 221 | String key = args.get("id").toString(); 222 | String host = args.get("host").toString(); 223 | int port = (int)args.get("port"); 224 | String username = args.get("username").toString(); 225 | 226 | JSch jsch = new JSch(); 227 | 228 | String password = ""; 229 | if (args.get("passwordOrKey").getClass() == args.getClass()) { 230 | HashMap keyPairs = (HashMap) args.get("passwordOrKey"); 231 | byte[] privateKey = keyPairs.containsKey("privateKey") ? keyPairs.get("privateKey").toString().getBytes(): null; 232 | byte[] publicKey = keyPairs.containsKey("publicKey") ? keyPairs.get("publicKey").toString().getBytes(): null; 233 | byte[] passphrase = keyPairs.containsKey("passphrase") ? keyPairs.get("passphrase").toString().getBytes(): null; 234 | jsch.addIdentity("default", privateKey, publicKey, passphrase); 235 | 236 | } else { 237 | password = args.get("passwordOrKey").toString(); 238 | } 239 | 240 | Session session = jsch.getSession(username, host, port); 241 | 242 | if (password.length() > 0) 243 | session.setPassword(password); 244 | 245 | Properties properties = new Properties(); 246 | properties.setProperty("StrictHostKeyChecking", "no"); 247 | session.setConfig(properties); 248 | session.connect(); 249 | 250 | if (session.isConnected()) { 251 | SSHClient client = new SSHClient(); 252 | client._session = session; 253 | client._key = key; 254 | clientPool.put(key, client); 255 | 256 | Log.d(LOGTAG, "Session connected"); 257 | result.success("session_connected"); 258 | } 259 | } catch (Exception error) { 260 | Log.e(LOGTAG, "Connection failed: " + error.getMessage()); 261 | result.error("connection_failure", error.getMessage(), null); 262 | } 263 | } 264 | }).start(); 265 | } 266 | 267 | private void execute(final HashMap args, final Result result) { 268 | new Thread(new Runnable() { 269 | public void run() { 270 | try { 271 | SSHClient client = getClient(args.get("id").toString(), result); 272 | if (client == null) 273 | return; 274 | 275 | Session session = client._session; 276 | ChannelExec channel = (ChannelExec) session.openChannel("exec"); 277 | 278 | InputStream in = channel.getInputStream(); 279 | 280 | channel.setCommand(args.get("cmd").toString()); 281 | channel.connect(); 282 | 283 | String line, response = ""; 284 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 285 | while ((line = reader.readLine()) != null) { 286 | response += line + "\r\n"; 287 | } 288 | 289 | result.success(response); 290 | } catch (Exception error) { 291 | Log.e(LOGTAG, "Error executing command: " + error.getMessage()); 292 | result.error("execute_failure", error.getMessage(), null); 293 | } 294 | } 295 | }).start(); 296 | } 297 | 298 | private void portForwardL(final HashMap args, final Result result) { 299 | new Thread(new Runnable() { 300 | public void run() { 301 | try { 302 | SSHClient client = getClient(args.get("id").toString(), result); 303 | if (client == null) 304 | return; 305 | 306 | Session session = client._session; 307 | int rport = Integer.parseInt(args.get("rport").toString()); 308 | int lport = Integer.parseInt(args.get("lport").toString()); 309 | String rhost = args.get("rhost").toString(); 310 | int assinged_port=session.setPortForwardingL(lport, rhost, rport); 311 | 312 | result.success(Integer.toString(assinged_port)); 313 | } catch (JSchException error) { 314 | Log.e(LOGTAG, "Error connecting portforwardL:" + error.getMessage()); 315 | result.error("portforwardL_failure", error.getMessage(), null); 316 | } 317 | } 318 | }).start(); 319 | } 320 | 321 | private void startShell(final HashMap args, final Result result) { 322 | new Thread(new Runnable() { 323 | public void run() { 324 | try { 325 | String key = args.get("id").toString(); 326 | SSHClient client = getClient(args.get("id").toString(), result); 327 | if (client == null) 328 | return; 329 | 330 | Session session = client._session; 331 | Channel channel = session.openChannel("shell"); 332 | 333 | InputStream in = channel.getInputStream(); 334 | 335 | ((ChannelShell)channel).setPtyType(args.get("ptyType").toString()); 336 | channel.connect(); 337 | 338 | client._channel = channel; 339 | client._bufferedReader = new BufferedReader(new InputStreamReader(in)); 340 | client._dataOutputStream = new DataOutputStream(channel.getOutputStream()); 341 | 342 | result.success("shell_started"); 343 | 344 | String line; 345 | while (client._bufferedReader != null && (line = client._bufferedReader.readLine()) != null) { 346 | Map map = new HashMap<>(); 347 | map.put("name", "Shell"); 348 | map.put("key", key); 349 | map.put("value", line + '\n'); 350 | sendEvent(map); 351 | } 352 | 353 | } catch (Exception error) { 354 | Log.e(LOGTAG, "Error starting shell: " + error.getMessage()); 355 | result.error("shell_failure", error.getMessage(), null); 356 | } 357 | } 358 | }).start(); 359 | } 360 | 361 | private void writeToShell(final HashMap args, final Result result) { 362 | new Thread(new Runnable() { 363 | public void run() { 364 | try { 365 | SSHClient client = getClient(args.get("id").toString(), result); 366 | if (client == null) 367 | return; 368 | 369 | client._dataOutputStream.writeBytes(args.get("cmd").toString()); 370 | client._dataOutputStream.flush(); 371 | result.success("write_success"); 372 | } catch (IOException error) { 373 | Log.e(LOGTAG, "Error writing to shell:" + error.getMessage()); 374 | result.error("write_failure", error.getMessage(), null); 375 | } 376 | } 377 | }).start(); 378 | } 379 | 380 | private void closeShell(final HashMap args) { 381 | new Thread(new Runnable() { 382 | public void run() { 383 | try { 384 | SSHClient client = clientPool.get(args.get("id")); 385 | if (client == null) 386 | return; 387 | 388 | if (client._channel != null) { 389 | client._channel.disconnect(); 390 | } 391 | 392 | if (client._dataOutputStream != null) { 393 | client._dataOutputStream.flush(); 394 | client._dataOutputStream.close(); 395 | } 396 | 397 | if (client._bufferedReader != null) { 398 | client._bufferedReader.close(); 399 | client._bufferedReader = null; 400 | } 401 | } catch (IOException error) { 402 | Log.e(LOGTAG, "Error closing shell:" + error.getMessage()); 403 | } 404 | } 405 | }).start(); 406 | } 407 | 408 | private void connectSFTP(final HashMap args, final Result result) { 409 | new Thread(new Runnable() { 410 | public void run() { 411 | try { 412 | SSHClient client = getClient(args.get("id").toString(), result); 413 | if (client == null) 414 | return; 415 | 416 | ChannelSftp channelSftp = (ChannelSftp) client._session.openChannel("sftp"); 417 | channelSftp.connect(); 418 | client._sftpSession = channelSftp; 419 | result.success("sftp_connected"); 420 | } catch (JSchException error) { 421 | Log.e(LOGTAG, "Error connecting SFTP:" + error.getMessage()); 422 | result.error("sftp_failure", error.getMessage(), null); 423 | } 424 | } 425 | }).start(); 426 | } 427 | 428 | private void sftpLs(final HashMap args, final Result result) { 429 | new Thread(new Runnable() { 430 | public void run() { 431 | try { 432 | SSHClient client = clientPool.get(args.get("id")); 433 | ChannelSftp channelSftp = client._sftpSession; 434 | 435 | Vector files = channelSftp.ls(args.get("path").toString()); 436 | List> response = new ArrayList<>(); 437 | 438 | for (LsEntry file: files) { 439 | String filename = file.getFilename(); 440 | if (filename.trim().equals(".") || filename.trim().equals("..")) 441 | continue; 442 | 443 | Map f = new HashMap<>(); 444 | f.put("filename", filename); 445 | f.put("isDirectory", file.getAttrs().isDir()); 446 | Date datetime = new Date(file.getAttrs().getMTime() * 1000L); 447 | f.put("modificationDate", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(datetime)); 448 | datetime = new Date(file.getAttrs().getATime() * 1000L); 449 | f.put("lastAccess", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(datetime)); 450 | f.put("fileSize", file.getAttrs().getSize()); 451 | f.put("ownerUserID", file.getAttrs().getUId()); 452 | f.put("ownerGroupID", file.getAttrs().getGId()); 453 | f.put("permissions", file.getAttrs().getPermissionsString()); 454 | f.put("flags", file.getAttrs().getFlags()); 455 | 456 | response.add(f); 457 | } 458 | result.success(response); 459 | } catch (SftpException error) { 460 | Log.e(LOGTAG, "Failed to list path " + error.getMessage()); 461 | result.error("ls_failure", error.getMessage(), null); 462 | } 463 | } 464 | }).start(); 465 | } 466 | 467 | private void sftpRename(final HashMap args, final Result result) { 468 | new Thread(new Runnable() { 469 | public void run() { 470 | try { 471 | SSHClient client = clientPool.get(args.get("id")); 472 | ChannelSftp channelSftp = client._sftpSession; 473 | channelSftp.rename(args.get("oldPath").toString(), args.get("newPath").toString()); 474 | result.success("rename_success"); 475 | } catch (SftpException error) { 476 | Log.e(LOGTAG, "Failed to rename path " + args.get("oldPath").toString()); 477 | result.error("rename_failure", error.getMessage(), null); 478 | } 479 | } 480 | }).start(); 481 | } 482 | 483 | private void sftpMkdir(final HashMap args, final Result result) { 484 | new Thread(new Runnable() { 485 | public void run() { 486 | try { 487 | SSHClient client = clientPool.get(args.get("id")); 488 | ChannelSftp channelSftp = client._sftpSession; 489 | channelSftp.mkdir(args.get("path").toString()); 490 | result.success("mkdir_success"); 491 | } catch (SftpException error) { 492 | Log.e(LOGTAG, "Failed to create directory " + args.get("path").toString()); 493 | result.error("mkdir_success", error.getMessage(), null); 494 | } 495 | } 496 | }).start(); 497 | } 498 | 499 | private void sftpRm(final HashMap args, final Result result) { 500 | new Thread(new Runnable() { 501 | public void run() { 502 | try { 503 | SSHClient client = clientPool.get(args.get("id")); 504 | ChannelSftp channelSftp = client._sftpSession; 505 | channelSftp.rm(args.get("path").toString()); 506 | result.success("rm_success"); 507 | } catch (SftpException error) { 508 | Log.e(LOGTAG, "Failed to remove " + args.get("path").toString()); 509 | result.error("rm_success", error.getMessage(), null); 510 | } 511 | } 512 | }).start(); 513 | } 514 | 515 | private void sftpRmdir(final HashMap args, final Result result) { 516 | new Thread(new Runnable() { 517 | public void run() { 518 | try { 519 | SSHClient client = clientPool.get(args.get("id")); 520 | ChannelSftp channelSftp = client._sftpSession; 521 | channelSftp.rmdir(args.get("path").toString()); 522 | result.success("rmdir_success"); 523 | } catch (SftpException error) { 524 | Log.e(LOGTAG, "Failed to remove " + args.get("path").toString()); 525 | result.error("rmdir_failure", error.getMessage(), null); 526 | } 527 | } 528 | }).start(); 529 | } 530 | 531 | private void sftpDownload(final HashMap args, final Result result) { 532 | new Thread(new Runnable() { 533 | public void run() { 534 | try { 535 | SSHClient client = clientPool.get(args.get("id")); 536 | client._downloadContinue = true; 537 | ChannelSftp channelSftp = client._sftpSession; 538 | String path = args.get("path").toString(); 539 | String toPath = args.get("toPath").toString(); 540 | channelSftp.get(path, toPath, new progressMonitor(args.get("id").toString(), "DownloadProgress")); 541 | if (client._downloadContinue == true) 542 | result.success(toPath + '/' + (new File(path).getName())); 543 | else 544 | result.success("download_canceled"); 545 | } catch (SftpException error) { 546 | Log.e(LOGTAG, "Failed to download " + args.get("path").toString()); 547 | result.error("download_failure", error.getMessage(), null); 548 | } 549 | } 550 | }).start(); 551 | } 552 | 553 | private void sftpUpload(final HashMap args, final Result result) { 554 | new Thread(new Runnable() { 555 | public void run() { 556 | try { 557 | SSHClient client = clientPool.get(args.get("id")); 558 | client._uploadContinue = true; 559 | ChannelSftp channelSftp = client._sftpSession; 560 | String path = args.get("path").toString(); 561 | String toPath = args.get("toPath").toString(); 562 | channelSftp.put(path, toPath + '/' + (new File(path)).getName(), 563 | new progressMonitor(args.get("id").toString(), "UploadProgress"), ChannelSftp.OVERWRITE); 564 | if (client._uploadContinue == true) 565 | result.success("upload_success"); 566 | else 567 | result.success("upload_canceled"); 568 | } catch (SftpException error) { 569 | Log.e(LOGTAG, "Failed to upload " + args.get("path").toString()); 570 | result.error("upload_failure", error.getMessage(), null); 571 | } 572 | } 573 | }).start(); 574 | } 575 | 576 | private void sftpCancelDownload(final HashMap args) { 577 | SSHClient client = clientPool.get(args.get("id")); 578 | client._downloadContinue = false; 579 | } 580 | 581 | private void sftpCancelUpload(final HashMap args) { 582 | SSHClient client = clientPool.get(args.get("id")); 583 | client._uploadContinue = false; 584 | } 585 | 586 | private void disconnectSFTP(final HashMap args) { 587 | new Thread(new Runnable() { 588 | public void run() { 589 | SSHClient client = clientPool.get(args.get("id")); 590 | if (client._sftpSession != null) { 591 | client._sftpSession.disconnect(); 592 | } 593 | } 594 | }).start(); 595 | } 596 | 597 | private void disconnect(final HashMap args) { 598 | this.closeShell(args); 599 | this.disconnectSFTP(args); 600 | 601 | SSHClient client = clientPool.get(args.get("id")); 602 | if (client == null) 603 | return; 604 | client._session.disconnect(); 605 | } 606 | 607 | private void isConnected(final HashMap args, final Result result) { 608 | SSHClient client = clientPool.get(args.get("id")); 609 | if (client == null) { 610 | result.success("false"); 611 | } else if ( client._session == null || ! client._session.isConnected()) { 612 | result.success("false"); 613 | } else { 614 | result.success("true"); 615 | } 616 | } 617 | 618 | private void sendEvent(Map event) { 619 | if (eventSink != null) 620 | eventSink.success(event); 621 | } 622 | 623 | private class progressMonitor implements SftpProgressMonitor { 624 | private long max = 0; 625 | private long count = 0; 626 | private long completedPerc = 0; 627 | private String key; 628 | private String name; 629 | 630 | public progressMonitor(String key, String name) { 631 | this.key = key; 632 | this.name = name; 633 | } 634 | 635 | public void init(int arg0, String arg1, String arg2, long arg3) { 636 | this.max = arg3; 637 | } 638 | 639 | public boolean count(long arg0) { 640 | SSHClient client = clientPool.get(this.key); 641 | this.count += arg0; 642 | long newPerc = this.count * 100 / max; 643 | if(newPerc % 5 == 0 && newPerc > this.completedPerc) { 644 | this.completedPerc = newPerc; 645 | Map map = new HashMap<>(); 646 | map.put("name", this.name); 647 | map.put("key", this.key); 648 | map.put("value", this.completedPerc); 649 | sendEvent(map); 650 | } 651 | boolean con; 652 | if (this.name.equals("DownloadProgress")) { 653 | con = client._downloadContinue; 654 | } else { 655 | con = client._uploadContinue; 656 | } 657 | return con; 658 | } 659 | 660 | public void end() { 661 | } 662 | } 663 | } 664 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | .flutter-plugins-dependencies 11 | 12 | flutter_export_environment.sh 13 | Flutter.podspec -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 3b309bda072a6b326e8aa4591a5836af600923ce 8 | channel: beta 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # ssh_example 2 | 3 | Demonstrates how to use the ssh plugin. 4 | 5 | ## Install 6 | 7 | ``` 8 | flutter packages get 9 | ``` 10 | 11 | ## Run 12 | 13 | ``` 14 | flutter run 15 | ``` -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /example/android/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | android 4 | Project android created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.buildship.core.gradleprojectbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.buildship.core.gradleprojectnature 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/android/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir= 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /example/android/app/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/android/app/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | app 4 | Project app created by Buildship. 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.buildship.core.gradleprojectbuilder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.buildship.core.gradleprojectnature 22 | 23 | 24 | -------------------------------------------------------------------------------- /example/android/app/.settings/org.eclipse.buildship.core.prefs: -------------------------------------------------------------------------------- 1 | connection.project.dir=.. 2 | eclipse.preferences.version=1 3 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "sq.flutter.sshexample" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/sq/flutter/sshexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package sq.flutter.sshexample; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | 18 | // gradle.projectsEvaluated { 19 | // tasks.withType(JavaCompile) { 20 | // options.compilerArgs << "-Xlint:unchecked" 21 | // } 22 | // } 23 | } 24 | 25 | rootProject.buildDir = '../build' 26 | subprojects { 27 | project.buildDir = "${rootProject.buildDir}/${project.name}" 28 | } 29 | subprojects { 30 | project.evaluationDependsOn(':app') 31 | } 32 | 33 | task clean(type: Delete) { 34 | delete rootProject.buildDir 35 | } 36 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | target-platform=android-arm64 -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | # Flutter Pod 37 | 38 | copied_flutter_dir = File.join(__dir__, 'Flutter') 39 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 40 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 41 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 42 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 43 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 44 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 45 | 46 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 47 | unless File.exist?(generated_xcode_build_settings_path) 48 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 49 | end 50 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 51 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 52 | 53 | unless File.exist?(copied_framework_path) 54 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 55 | end 56 | unless File.exist?(copied_podspec_path) 57 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 58 | end 59 | end 60 | 61 | # Keep pod path relative so it can be checked into Podfile.lock. 62 | pod 'Flutter', :path => 'Flutter' 63 | 64 | # Plugin Pods 65 | 66 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 67 | # referring to absolute paths on developers' machines. 68 | system('rm -rf .symlinks') 69 | system('mkdir -p .symlinks/plugins') 70 | plugin_pods = parse_KV_file('../.flutter-plugins') 71 | plugin_pods.each do |name, path| 72 | symlink = File.join('.symlinks', 'plugins', name) 73 | File.symlink(path, symlink) 74 | pod name, :path => File.join(symlink, 'ios') 75 | end 76 | end 77 | 78 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 79 | install! 'cocoapods', :disable_input_output_paths => true 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - NMSSH (2.3.1) 4 | - path_provider (0.0.1): 5 | - Flutter 6 | - ssh (0.0.7): 7 | - Flutter 8 | - NMSSH 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 13 | - ssh (from `.symlinks/plugins/ssh/ios`) 14 | 15 | SPEC REPOS: 16 | https://github.com/CocoaPods/Specs.git: 17 | - NMSSH 18 | 19 | EXTERNAL SOURCES: 20 | Flutter: 21 | :path: Flutter 22 | path_provider: 23 | :path: ".symlinks/plugins/path_provider/ios" 24 | ssh: 25 | :path: ".symlinks/plugins/ssh/ios" 26 | 27 | SPEC CHECKSUMS: 28 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 29 | NMSSH: cd689ae535a1581a6dd9fd4600ff71247df510a6 30 | path_provider: f96fff6166a8867510d2c25fdcc346327cc4b259 31 | ssh: b48add2c5d3baf9c547674ca86a02fe67dede30d 32 | 33 | PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271 34 | 35 | COCOAPODS: 1.9.0 36 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 3BFD9F155BFD2812E05F1421 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4EDC94F555C12F0DBC65A05 /* libPods-Runner.a */; }; 15 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 16 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 17 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 18 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 19 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 20 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 21 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 22 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 2147483647; 29 | dstPath = ""; 30 | dstSubfolderSpec = 10; 31 | files = ( 32 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 33 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 34 | ); 35 | name = "Embed Frameworks"; 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXCopyFilesBuildPhase section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 42 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 43 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 44 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 45 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 46 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 47 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 48 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 49 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 50 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 51 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 53 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 54 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 56 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | AF1BEAF72857AD35E9C18449 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 58 | E4EDC94F555C12F0DBC65A05 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | FCE9DA87189C8322B49758AD /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 60 | /* End PBXFileReference section */ 61 | 62 | /* Begin PBXFrameworksBuildPhase section */ 63 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 64 | isa = PBXFrameworksBuildPhase; 65 | buildActionMask = 2147483647; 66 | files = ( 67 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 68 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 69 | 3BFD9F155BFD2812E05F1421 /* libPods-Runner.a in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 13EFE2B506E6B9319161F70D /* Frameworks */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | E4EDC94F555C12F0DBC65A05 /* libPods-Runner.a */, 80 | ); 81 | name = Frameworks; 82 | sourceTree = ""; 83 | }; 84 | 9740EEB11CF90186004384FC /* Flutter */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 3B80C3931E831B6300D905FE /* App.framework */, 88 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 89 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 90 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 91 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 92 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 93 | ); 94 | name = Flutter; 95 | sourceTree = ""; 96 | }; 97 | 97C146E51CF9000F007C117D = { 98 | isa = PBXGroup; 99 | children = ( 100 | 9740EEB11CF90186004384FC /* Flutter */, 101 | 97C146F01CF9000F007C117D /* Runner */, 102 | 97C146EF1CF9000F007C117D /* Products */, 103 | CE786E24DEAF8E8A58422110 /* Pods */, 104 | 13EFE2B506E6B9319161F70D /* Frameworks */, 105 | ); 106 | sourceTree = ""; 107 | }; 108 | 97C146EF1CF9000F007C117D /* Products */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 97C146EE1CF9000F007C117D /* Runner.app */, 112 | ); 113 | name = Products; 114 | sourceTree = ""; 115 | }; 116 | 97C146F01CF9000F007C117D /* Runner */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 120 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 121 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 122 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 123 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 124 | 97C147021CF9000F007C117D /* Info.plist */, 125 | 97C146F11CF9000F007C117D /* Supporting Files */, 126 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 127 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 128 | ); 129 | path = Runner; 130 | sourceTree = ""; 131 | }; 132 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 97C146F21CF9000F007C117D /* main.m */, 136 | ); 137 | name = "Supporting Files"; 138 | sourceTree = ""; 139 | }; 140 | CE786E24DEAF8E8A58422110 /* Pods */ = { 141 | isa = PBXGroup; 142 | children = ( 143 | FCE9DA87189C8322B49758AD /* Pods-Runner.debug.xcconfig */, 144 | AF1BEAF72857AD35E9C18449 /* Pods-Runner.release.xcconfig */, 145 | ); 146 | name = Pods; 147 | sourceTree = ""; 148 | }; 149 | /* End PBXGroup section */ 150 | 151 | /* Begin PBXNativeTarget section */ 152 | 97C146ED1CF9000F007C117D /* Runner */ = { 153 | isa = PBXNativeTarget; 154 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 155 | buildPhases = ( 156 | 8D52E3BF535D8A8CF68DDE15 /* [CP] Check Pods Manifest.lock */, 157 | 9740EEB61CF901F6004384FC /* Run Script */, 158 | 97C146EA1CF9000F007C117D /* Sources */, 159 | 97C146EB1CF9000F007C117D /* Frameworks */, 160 | 97C146EC1CF9000F007C117D /* Resources */, 161 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 162 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 163 | 44731481D050F8D1B08A810C /* [CP] Embed Pods Frameworks */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = Runner; 170 | productName = Runner; 171 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 172 | productType = "com.apple.product-type.application"; 173 | }; 174 | /* End PBXNativeTarget section */ 175 | 176 | /* Begin PBXProject section */ 177 | 97C146E61CF9000F007C117D /* Project object */ = { 178 | isa = PBXProject; 179 | attributes = { 180 | LastUpgradeCheck = 0910; 181 | ORGANIZATIONNAME = "The Chromium Authors"; 182 | TargetAttributes = { 183 | 97C146ED1CF9000F007C117D = { 184 | CreatedOnToolsVersion = 7.3.1; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 189 | compatibilityVersion = "Xcode 3.2"; 190 | developmentRegion = English; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = 97C146E51CF9000F007C117D; 197 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 97C146ED1CF9000F007C117D /* Runner */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | 97C146EC1CF9000F007C117D /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 212 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 213 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 214 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 215 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXShellScriptBuildPhase section */ 222 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 223 | isa = PBXShellScriptBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | ); 227 | inputPaths = ( 228 | ); 229 | name = "Thin Binary"; 230 | outputPaths = ( 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 235 | }; 236 | 44731481D050F8D1B08A810C /* [CP] Embed Pods Frameworks */ = { 237 | isa = PBXShellScriptBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | inputPaths = ( 242 | ); 243 | name = "[CP] Embed Pods Frameworks"; 244 | outputPaths = ( 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | shellPath = /bin/sh; 248 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 249 | showEnvVarsInLog = 0; 250 | }; 251 | 8D52E3BF535D8A8CF68DDE15 /* [CP] Check Pods Manifest.lock */ = { 252 | isa = PBXShellScriptBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | inputFileListPaths = ( 257 | ); 258 | inputPaths = ( 259 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 260 | "${PODS_ROOT}/Manifest.lock", 261 | ); 262 | name = "[CP] Check Pods Manifest.lock"; 263 | outputFileListPaths = ( 264 | ); 265 | outputPaths = ( 266 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | shellPath = /bin/sh; 270 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 271 | showEnvVarsInLog = 0; 272 | }; 273 | 9740EEB61CF901F6004384FC /* Run Script */ = { 274 | isa = PBXShellScriptBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | ); 278 | inputPaths = ( 279 | ); 280 | name = "Run Script"; 281 | outputPaths = ( 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | shellPath = /bin/sh; 285 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 286 | }; 287 | /* End PBXShellScriptBuildPhase section */ 288 | 289 | /* Begin PBXSourcesBuildPhase section */ 290 | 97C146EA1CF9000F007C117D /* Sources */ = { 291 | isa = PBXSourcesBuildPhase; 292 | buildActionMask = 2147483647; 293 | files = ( 294 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 295 | 97C146F31CF9000F007C117D /* main.m in Sources */, 296 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 297 | ); 298 | runOnlyForDeploymentPostprocessing = 0; 299 | }; 300 | /* End PBXSourcesBuildPhase section */ 301 | 302 | /* Begin PBXVariantGroup section */ 303 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 304 | isa = PBXVariantGroup; 305 | children = ( 306 | 97C146FB1CF9000F007C117D /* Base */, 307 | ); 308 | name = Main.storyboard; 309 | sourceTree = ""; 310 | }; 311 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 312 | isa = PBXVariantGroup; 313 | children = ( 314 | 97C147001CF9000F007C117D /* Base */, 315 | ); 316 | name = LaunchScreen.storyboard; 317 | sourceTree = ""; 318 | }; 319 | /* End PBXVariantGroup section */ 320 | 321 | /* Begin XCBuildConfiguration section */ 322 | 97C147031CF9000F007C117D /* Debug */ = { 323 | isa = XCBuildConfiguration; 324 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_ANALYZER_NONNULL = YES; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_COMMA = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_EMPTY_BODY = YES; 338 | CLANG_WARN_ENUM_CONVERSION = YES; 339 | CLANG_WARN_INFINITE_RECURSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = dwarf; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | ENABLE_TESTABILITY = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu99; 355 | GCC_DYNAMIC_NO_PIC = NO; 356 | GCC_NO_COMMON_BLOCKS = YES; 357 | GCC_OPTIMIZATION_LEVEL = 0; 358 | GCC_PREPROCESSOR_DEFINITIONS = ( 359 | "DEBUG=1", 360 | "$(inherited)", 361 | ); 362 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 363 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 364 | GCC_WARN_UNDECLARED_SELECTOR = YES; 365 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 366 | GCC_WARN_UNUSED_FUNCTION = YES; 367 | GCC_WARN_UNUSED_VARIABLE = YES; 368 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 369 | MTL_ENABLE_DEBUG_INFO = YES; 370 | ONLY_ACTIVE_ARCH = YES; 371 | SDKROOT = iphoneos; 372 | TARGETED_DEVICE_FAMILY = "1,2"; 373 | }; 374 | name = Debug; 375 | }; 376 | 97C147041CF9000F007C117D /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 379 | buildSettings = { 380 | ALWAYS_SEARCH_USER_PATHS = NO; 381 | CLANG_ANALYZER_NONNULL = YES; 382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 383 | CLANG_CXX_LIBRARY = "libc++"; 384 | CLANG_ENABLE_MODULES = YES; 385 | CLANG_ENABLE_OBJC_ARC = YES; 386 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 387 | CLANG_WARN_BOOL_CONVERSION = YES; 388 | CLANG_WARN_COMMA = YES; 389 | CLANG_WARN_CONSTANT_CONVERSION = YES; 390 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 391 | CLANG_WARN_EMPTY_BODY = YES; 392 | CLANG_WARN_ENUM_CONVERSION = YES; 393 | CLANG_WARN_INFINITE_RECURSION = YES; 394 | CLANG_WARN_INT_CONVERSION = YES; 395 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 396 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 397 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 398 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 399 | CLANG_WARN_STRICT_PROTOTYPES = YES; 400 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 401 | CLANG_WARN_UNREACHABLE_CODE = YES; 402 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 403 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 404 | COPY_PHASE_STRIP = NO; 405 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 406 | ENABLE_NS_ASSERTIONS = NO; 407 | ENABLE_STRICT_OBJC_MSGSEND = YES; 408 | GCC_C_LANGUAGE_STANDARD = gnu99; 409 | GCC_NO_COMMON_BLOCKS = YES; 410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 412 | GCC_WARN_UNDECLARED_SELECTOR = YES; 413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 414 | GCC_WARN_UNUSED_FUNCTION = YES; 415 | GCC_WARN_UNUSED_VARIABLE = YES; 416 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 417 | MTL_ENABLE_DEBUG_INFO = NO; 418 | SDKROOT = iphoneos; 419 | TARGETED_DEVICE_FAMILY = "1,2"; 420 | VALIDATE_PRODUCT = YES; 421 | }; 422 | name = Release; 423 | }; 424 | 97C147061CF9000F007C117D /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 427 | buildSettings = { 428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 429 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 430 | ENABLE_BITCODE = NO; 431 | FRAMEWORK_SEARCH_PATHS = ( 432 | "$(inherited)", 433 | "$(PROJECT_DIR)/Flutter", 434 | ); 435 | INFOPLIST_FILE = Runner/Info.plist; 436 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 437 | LIBRARY_SEARCH_PATHS = ( 438 | "$(inherited)", 439 | "$(PROJECT_DIR)/Flutter", 440 | ); 441 | PRODUCT_BUNDLE_IDENTIFIER = sq.flutter.sshExample; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | VERSIONING_SYSTEM = "apple-generic"; 444 | }; 445 | name = Debug; 446 | }; 447 | 97C147071CF9000F007C117D /* Release */ = { 448 | isa = XCBuildConfiguration; 449 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 450 | buildSettings = { 451 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 452 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 453 | ENABLE_BITCODE = NO; 454 | FRAMEWORK_SEARCH_PATHS = ( 455 | "$(inherited)", 456 | "$(PROJECT_DIR)/Flutter", 457 | ); 458 | INFOPLIST_FILE = Runner/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | LIBRARY_SEARCH_PATHS = ( 461 | "$(inherited)", 462 | "$(PROJECT_DIR)/Flutter", 463 | ); 464 | PRODUCT_BUNDLE_IDENTIFIER = sq.flutter.sshExample; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | VERSIONING_SYSTEM = "apple-generic"; 467 | }; 468 | name = Release; 469 | }; 470 | /* End XCBuildConfiguration section */ 471 | 472 | /* Begin XCConfigurationList section */ 473 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 474 | isa = XCConfigurationList; 475 | buildConfigurations = ( 476 | 97C147031CF9000F007C117D /* Debug */, 477 | 97C147041CF9000F007C117D /* Release */, 478 | ); 479 | defaultConfigurationIsVisible = 0; 480 | defaultConfigurationName = Release; 481 | }; 482 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 483 | isa = XCConfigurationList; 484 | buildConfigurations = ( 485 | 97C147061CF9000F007C117D /* Debug */, 486 | 97C147071CF9000F007C117D /* Release */, 487 | ); 488 | defaultConfigurationIsVisible = 0; 489 | defaultConfigurationName = Release; 490 | }; 491 | /* End XCConfigurationList section */ 492 | }; 493 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 494 | } 495 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ssh_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:ssh/ssh.dart'; 6 | import 'package:path_provider/path_provider.dart'; 7 | 8 | void main() => runApp(new MyApp()); 9 | 10 | class MyApp extends StatefulWidget { 11 | @override 12 | _MyAppState createState() => new _MyAppState(); 13 | } 14 | 15 | class _MyAppState extends State { 16 | String _result = ''; 17 | List _array; 18 | 19 | Future onClickCmd() async { 20 | var client = new SSHClient( 21 | host: "my.sshtest", 22 | port: 22, 23 | username: "sha", 24 | passwordOrKey: "Password01.", 25 | ); 26 | 27 | String result; 28 | try { 29 | result = await client.connect(); 30 | if (result == "session_connected") result = await client.execute("ps"); 31 | client.disconnect(); 32 | } on PlatformException catch (e) { 33 | print('Error: ${e.code}\nError Message: ${e.message}'); 34 | } 35 | 36 | setState(() { 37 | _result = result; 38 | _array = null; 39 | }); 40 | } 41 | 42 | Future onClickShell() async { 43 | var client = new SSHClient( 44 | host: "my.sshtest", 45 | port: 22, 46 | username: "sha", 47 | passwordOrKey: { 48 | "privateKey": """-----BEGIN RSA PRIVATE KEY----- 49 | MIIEpAIBAAKCAQEA2DdFSeWG8wOHddRpOhf4FRqksJITr59iXdNrXq+n79QFN1g4 50 | bvRG9zCDmyLb8EF+gah78dpJsGZVIltmfYWpsk7ok9GT/foCB1d2E6DbEU6mBIPe 51 | OLxYOqyiea8mi7iGt9BvAB4Mj+v2LnhK4O2BB6PTU4KLjSgMdqtV/EGctLdK+JEU 52 | 5Vo/tDvtF8jmUYGV57V8VovgQJHxOWcT9Mgz+c5EcyVvhwvA/88emYkZ49cn4N0A 53 | az7YsOTHItvbhxf9xI3bBwxoPvfLx/G0n48TeY33D0qu/qjAloOfqPMHwj+upn/K 54 | RrAawl2N1MObCc5/q1WYTftd5+uoQsB1RN7ptQIDAQABAoIBAQCzKBkhwi6v7pyv 55 | 5fHLUVEfK5SLOn9VZpv7YtP1AVgGQYiQ82jPh1nGOUzTn27fBWXtyc3p+RZWNHUW 56 | ouWp3LdgKEJPObmHGUHVE4OjgAYFsUWfOCVKncX92E5IxfkKjTwT04Imdr+yAbNb 57 | jhF9j077JaRV7jX0INsy+YWmIDfZBQHdR4gpip6ye70yc4p0M7DbrhjEFi6cvf5b 58 | OaSsbKAunxZte42RYY1ap6GmEii5B/wWe37176jBUrCeQzN9poTSFEv99+Av6M3R 59 | yyBD1PyawR+dPCAicvIY88ME4fAJSi6Gp8Kmievq7bXnGw8ICWggVSnl0TBYhwSY 60 | SN8mBr2BAoGBAPNNQ+77kEkwsA0pzZljbwDhJ03jATsWpA4yN4S3Gz456ZUDxode 61 | lbHERy7RR8l6EunSRdlWGVW9d/8uXBKsvp78hZnJkUE1fLCP+5UH1DVYn+hSYhjj 62 | g9lnQXbKpXm5tpABiM7+sMq+pC2N6K8yQ7P33TXCcRCWpjK0OJcEVxq/AoGBAOOA 63 | HNlZe8gQeH3OrQWKEJjgF6oQ9pGdRgJJctdSHDsqP8cPV7BuiYaTh/Q+R+HIueJ+ 64 | 3abGLkRqxbNb5FIgX7HJRYLGlusccjd0L4OJ5upGDQJgJzQOryPFofihLvvNbY1K 65 | zLLNvvYoaWtXhSGusj5N9T6DuA6qxMs+0OwPeZyLAoGBAPHIjwInrTOO1uW97TvJ 66 | vL47Ajw8ozR9Q3t4HAQfk0s7cg1MOza7oDeQvsyf3Z8zWShUdmWNUpAKQf2trIJC 67 | eQy2Fm7GCTusU8WC0JlBtnltITxW4nWpY5XhLwVGTTuyeuKRI8vQ/w/8dFtw8xNn 68 | +DAY2hRartG1ZGRvBO3OumExAoGAeJuar7+417+joU7Ie39OfT2QTiDgFyKB0wSN 69 | VYm6XcNwPF/t5SM01ZuxH9NE2HZJ1cHcUGYQcUUJuqSkzsVK9j32E/akW9Cg3LVD 70 | 20BooxqwGupO3lJKl3RXAjCxb9zgj19wVfqtmmKiQL4NXmX3KQC7W4EJOv1dh0Ku 71 | D/fESTECgYBwWv9yveto6pP6/xbR9k/Jdgr+vXQ3BJVU3BOsD38SeSrZfMSNGqgx 72 | eiukCOIsRHYY7Qqi2vCJ62mwbHJ3RhSKKxcGpgzGX7KoGZS+bb5wb7RGNYK/mVaI 73 | pFkz72+8eA2cnbWUqHt9WqMUgUBYZTMESzQrTf7+q+0gWf49AZJ/QQ== 74 | -----END RSA PRIVATE KEY-----""", 75 | }, 76 | ); 77 | 78 | setState(() { 79 | _result = ""; 80 | _array = null; 81 | }); 82 | 83 | try { 84 | String result = await client.connect(); 85 | if (result == "session_connected") { 86 | result = await client.startShell( 87 | ptyType: "xterm", 88 | callback: (dynamic res) { 89 | setState(() { 90 | _result += res; 91 | }); 92 | }); 93 | 94 | if (result == "shell_started") { 95 | print(await client.writeToShell("echo hello > world\n")); 96 | print(await client.writeToShell("cat world\n")); 97 | new Future.delayed( 98 | const Duration(seconds: 5), 99 | () async => await client.closeShell(), 100 | ); 101 | } 102 | } 103 | } on PlatformException catch (e) { 104 | print('Error: ${e.code}\nError Message: ${e.message}'); 105 | } 106 | } 107 | 108 | Future onClickSFTP() async { 109 | var client = new SSHClient( 110 | host: "my.sshtest", 111 | port: 22, 112 | username: "sha", 113 | passwordOrKey: "Password01.", 114 | ); 115 | 116 | try { 117 | String result = await client.connect(); 118 | if (result == "session_connected") { 119 | result = await client.connectSFTP(); 120 | if (result == "sftp_connected") { 121 | var array = await client.sftpLs(); 122 | setState(() { 123 | _result = result; 124 | _array = array; 125 | }); 126 | 127 | print(await client.sftpMkdir("testsftp")); 128 | print(await client.sftpRename( 129 | oldPath: "testsftp", 130 | newPath: "testsftprename", 131 | )); 132 | print(await client.sftpRmdir("testsftprename")); 133 | 134 | Directory tempDir = await getTemporaryDirectory(); 135 | String tempPath = tempDir.path; 136 | var filePath = await client.sftpDownload( 137 | path: "testupload", 138 | toPath: tempPath, 139 | callback: (progress) async { 140 | print(progress); 141 | // if (progress == 20) await client.sftpCancelDownload(); 142 | }, 143 | ); 144 | 145 | print(await client.sftpRm("testupload")); 146 | 147 | print(await client.sftpUpload( 148 | path: filePath, 149 | toPath: ".", 150 | callback: (progress) async { 151 | print(progress); 152 | // if (progress == 30) await client.sftpCancelUpload(); 153 | }, 154 | )); 155 | 156 | print(await client.disconnectSFTP()); 157 | 158 | client.disconnect(); 159 | } 160 | } 161 | } on PlatformException catch (e) { 162 | print('Error: ${e.code}\nError Message: ${e.message}'); 163 | } 164 | } 165 | 166 | @override 167 | Widget build(BuildContext context) { 168 | Widget renderButtons() { 169 | return ButtonTheme( 170 | padding: EdgeInsets.all(5.0), 171 | child: ButtonBar( 172 | children: [ 173 | FlatButton( 174 | child: Text( 175 | 'Test command', 176 | style: TextStyle(color: Colors.white), 177 | ), 178 | onPressed: onClickCmd, 179 | color: Colors.blue, 180 | ), 181 | FlatButton( 182 | child: Text( 183 | 'Test shell', 184 | style: TextStyle(color: Colors.white), 185 | ), 186 | onPressed: onClickShell, 187 | color: Colors.blue, 188 | ), 189 | FlatButton( 190 | child: Text( 191 | 'Test SFTP', 192 | style: TextStyle(color: Colors.white), 193 | ), 194 | onPressed: onClickSFTP, 195 | color: Colors.blue, 196 | ), 197 | ], 198 | ), 199 | ); 200 | } 201 | 202 | return MaterialApp( 203 | home: Scaffold( 204 | appBar: AppBar( 205 | title: const Text('ssh plugin example app'), 206 | ), 207 | body: ListView( 208 | shrinkWrap: true, 209 | padding: EdgeInsets.all(15.0), 210 | children: [ 211 | Text( 212 | "Please edit the connection setting in the source code before clicking the test buttons"), 213 | renderButtons(), 214 | Text(_result), 215 | _array != null && _array.length > 0 216 | ? Column( 217 | children: _array.map((f) { 218 | return Text( 219 | "${f["filename"]} ${f["isDirectory"]} ${f["modificationDate"]} ${f["lastAccess"]} ${f["fileSize"]} ${f["ownerUserID"]} ${f["ownerGroupID"]} ${f["permissions"]} ${f["flags"]}"); 220 | }).toList(), 221 | ) 222 | : Container(), 223 | ], 224 | ), 225 | ), 226 | ); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ssh_example 2 | description: Demonstrates how to use the ssh plugin. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | environment: 13 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 14 | 15 | dependencies: 16 | flutter: 17 | sdk: flutter 18 | 19 | # The following adds the Cupertino Icons font to your application. 20 | # Use with the CupertinoIcons class for iOS style icons. 21 | cupertino_icons: ^0.1.2 22 | 23 | dev_dependencies: 24 | flutter_test: 25 | sdk: flutter 26 | 27 | ssh: 28 | path: ../ 29 | 30 | path_provider: ^0.4.1 31 | 32 | # For information on the generic Dart part of this file, see the 33 | # following page: https://www.dartlang.org/tools/pub/pubspec 34 | 35 | # The following section is specific to Flutter. 36 | flutter: 37 | 38 | # The following line ensures that the Material Icons font is 39 | # included with your application, so that you can use the icons in 40 | # the material Icons class. 41 | uses-material-design: true 42 | 43 | # To add assets to your application, add an assets section, like this: 44 | # assets: 45 | # - images/a_dot_burr.jpeg 46 | # - images/a_dot_ham.jpeg 47 | 48 | # An image asset can refer to one or more resolution-specific "variants", see 49 | # https://flutter.io/assets-and-images/#resolution-aware. 50 | 51 | # For details regarding adding assets from package dependencies, see 52 | # https://flutter.io/assets-and-images/#from-packages 53 | 54 | # To add custom fonts to your application, add a fonts section here, 55 | # in this "flutter" section. Each entry in this list should have a 56 | # "family" key with the font family name, and a "fonts" key with a 57 | # list giving the asset and other descriptors for the font. For 58 | # example: 59 | # fonts: 60 | # - family: Schyler 61 | # fonts: 62 | # - asset: fonts/Schyler-Regular.ttf 63 | # - asset: fonts/Schyler-Italic.ttf 64 | # style: italic 65 | # - family: Trajan Pro 66 | # fonts: 67 | # - asset: fonts/TrajanPro.ttf 68 | # - asset: fonts/TrajanPro_Bold.ttf 69 | # weight: 700 70 | # 71 | # For details regarding fonts from package dependencies, 72 | # see https://flutter.io/custom-fonts/#from-packages 73 | -------------------------------------------------------------------------------- /example/ssh_example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/ssh_example_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter_test/flutter_test.dart'; 9 | 10 | import 'package:ssh_example/main.dart'; 11 | 12 | void main() { 13 | testWidgets('Verify Platform version', (WidgetTester tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(new MyApp()); 16 | 17 | // Verify that platform version is retrieved. 18 | expect( 19 | find.byWidgetPredicate( 20 | (Widget widget) => 21 | widget is Text && widget.data.startsWith('Running on:'), 22 | ), 23 | findsOneWidget); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shaqian/flutter_ssh/d47202fc3a52013815c4b9d0c2e282ab34987280/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/SSHClient.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @protocol SSHClientDelegate 4 | - (void) shellEvent:(NSString *)event withKey:(NSString *)key; 5 | - (void) downloadProgressEvent:(int)event withKey:(NSString *)key; 6 | - (void) uploadProgressEvent:(int)event withKey:(NSString *)key; 7 | @end 8 | 9 | @interface SSHClient: NSObject 10 | @property(nonatomic, retain) NMSSHSession* _session; 11 | @property(nonatomic, retain) NMSFTP* _sftpSession; 12 | @property(nonatomic, retain) NSString* _key; 13 | @property (assign) BOOL _downloadContinue; 14 | @property (assign) BOOL _uploadContinue; 15 | 16 | @property (nonatomic, weak) id delegate; 17 | 18 | - (void) startShell:(NSString *)ptyType error:(NSError **)error; 19 | - (void) sftpDownload:(NSString *)path toPath:(NSString *)filePath error:(NSError **)error; 20 | - (BOOL) sftpUpload:(NSString *)filePath toPath:(NSString *)path; 21 | @end 22 | -------------------------------------------------------------------------------- /ios/Classes/SSHClient.m: -------------------------------------------------------------------------------- 1 | #import "SSHClient.h" 2 | 3 | @implementation SSHClient 4 | 5 | @synthesize delegate; 6 | 7 | @synthesize _session = _session; 8 | @synthesize _sftpSession = _sftpSession; 9 | @synthesize _downloadContinue = _downloadContinue; 10 | @synthesize _uploadContinue = _uploadContinue; 11 | @synthesize _key = _key; 12 | 13 | int downloadedPerc = 0; 14 | int uploadedPerc = 0; 15 | 16 | - (instancetype)init { 17 | if ((self = [super init])) { 18 | _session = [NMSSHSession alloc]; 19 | _sftpSession = [[NMSFTP alloc] init]; 20 | _key = [[NSString alloc] init]; 21 | _downloadContinue = false; 22 | _uploadContinue = false; 23 | } 24 | return self; 25 | } 26 | 27 | - (void) startShell:(NSString *)ptyType error:(NSError * __autoreleasing *)error { 28 | NMSSHChannelPtyTerminal type; 29 | NMSSHChannel *channel = _session.channel; 30 | channel.delegate = self; 31 | channel.requestPty = YES; 32 | 33 | NSArray *items = @[@"vanilla", @"vt100", @"vt102", @"vt220", @"ansi", @"xterm"]; 34 | NSUInteger item = [items indexOfObject:ptyType]; 35 | switch (item) { 36 | case 0: 37 | type = NMSSHChannelPtyTerminalVanilla; 38 | break; 39 | case 1: 40 | type = NMSSHChannelPtyTerminalVT100; 41 | break; 42 | case 2: 43 | type = NMSSHChannelPtyTerminalVT102; 44 | break; 45 | case 3: 46 | type = NMSSHChannelPtyTerminalVT220; 47 | break; 48 | case 4: 49 | type = NMSSHChannelPtyTerminalAnsi; 50 | break; 51 | default: 52 | type = NMSSHChannelPtyTerminalXterm; 53 | break; 54 | } 55 | 56 | channel.ptyTerminalType = type; 57 | dispatch_async(dispatch_get_main_queue(), ^ 58 | { 59 | [channel startShell:error]; 60 | }); 61 | } 62 | 63 | - (void)channel:(NMSSHChannel *)channel didReadData:(NSString *)message { 64 | [self.delegate shellEvent:message withKey:_key]; 65 | } 66 | 67 | - (void)channel:(NMSSHChannel *)channel didReadError:(NSString *)error { 68 | [self.delegate shellEvent:error withKey:_key]; 69 | } 70 | 71 | - (void) sftpDownload:(NSString *)path toPath:(NSString *)filePath error:(NSError **)error { 72 | _downloadContinue = true; 73 | downloadedPerc = 0; 74 | NSData* data = [_sftpSession contentsAtPath:path progress: ^BOOL (NSUInteger bytes, NSUInteger fileSize) { 75 | int newPerc = (int)(100.0f * bytes / fileSize); 76 | if (newPerc % 5 == 0 && newPerc > downloadedPerc) { 77 | downloadedPerc = newPerc; 78 | [self.delegate downloadProgressEvent:downloadedPerc withKey:self->_key]; 79 | } 80 | return self->_downloadContinue; 81 | }]; 82 | if (data) { 83 | [data writeToFile:filePath options:NSDataWritingAtomic error:error]; 84 | } 85 | } 86 | 87 | - (BOOL) sftpUpload:(NSString *)filePath toPath:(NSString *)path { 88 | _uploadContinue = true; 89 | uploadedPerc = 0; 90 | NSString *newPath = [NSString stringWithFormat:@"%@/%@",path, [filePath lastPathComponent]]; 91 | long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil][NSFileSize] longLongValue]; 92 | BOOL result = [self._sftpSession writeFileAtPath:filePath toFileAtPath:newPath progress: ^BOOL (NSUInteger bytes) { 93 | int newPerc = (int)(100.0f * bytes / fileSize); 94 | if (newPerc % 5 == 0 && newPerc > uploadedPerc) { 95 | uploadedPerc = newPerc; 96 | [self.delegate uploadProgressEvent:uploadedPerc withKey:self->_key]; 97 | } 98 | return self->_uploadContinue; 99 | }]; 100 | return result; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /ios/Classes/SshPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "SSHClient.h" 4 | 5 | @interface SshPlugin : NSObject 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Classes/SshPlugin.m: -------------------------------------------------------------------------------- 1 | #import "SshPlugin.h" 2 | 3 | @interface NSError (FlutterError) 4 | @property(readonly, nonatomic) FlutterError *flutterError; 5 | @end 6 | 7 | @implementation NSError (FlutterError) 8 | - (FlutterError *)flutterError { 9 | return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %d", (int)self.code] 10 | message:self.domain 11 | details:self.localizedDescription]; 12 | } 13 | @end 14 | 15 | @implementation SshPlugin { 16 | NSMutableDictionary* _clientPool; 17 | FlutterEventSink _eventSink; 18 | } 19 | 20 | + (void)registerWithRegistrar:(NSObject*)registrar { 21 | SshPlugin* instance = [[SshPlugin alloc] init]; 22 | FlutterMethodChannel* channel = [FlutterMethodChannel 23 | methodChannelWithName:@"ssh" 24 | binaryMessenger:[registrar messenger]]; 25 | [registrar addMethodCallDelegate:instance channel:channel]; 26 | 27 | FlutterEventChannel* shellChannel = [FlutterEventChannel 28 | eventChannelWithName:@"shell_sftp" 29 | binaryMessenger:[registrar messenger]]; 30 | [shellChannel setStreamHandler:instance]; 31 | } 32 | 33 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 34 | NSDictionary* args = call.arguments; 35 | if ([@"connectToHost" isEqualToString:call.method]) { 36 | NSNumber* port = args[@"port"]; 37 | [self connectToHost:args[@"host"] 38 | port:[port intValue] 39 | withUsername:args[@"username"] 40 | passwordOrKey:args[@"passwordOrKey"] 41 | withKey:args[@"id"] result:result]; 42 | } else if ([@"execute" isEqualToString:call.method]) { 43 | [self execute:args[@"cmd"] withKey:args[@"id"] result:result]; 44 | } else if ([@"startShell" isEqualToString:call.method]) { 45 | [self startShell:args[@"id"] ptyType:args[@"ptyType"] result:result]; 46 | } else if ([@"writeToShell" isEqualToString:call.method]) { 47 | [self writeToShell:args[@"cmd"] withKey:args[@"id"] result:result]; 48 | } else if ([@"closeShell" isEqualToString:call.method]) { 49 | [self closeShell:args[@"id"]]; 50 | } else if ([@"connectSFTP" isEqualToString:call.method]) { 51 | [self connectSFTP:args[@"id"] result:result]; 52 | } else if ([@"sftpLs" isEqualToString:call.method]) { 53 | [self sftpLs:args[@"path"] withKey:args[@"id"] result:result]; 54 | } else if ([@"sftpRename" isEqualToString:call.method]) { 55 | [self sftpRename:args[@"oldPath"] newPath:args[@"newPath"] withKey:args[@"id"] result:result]; 56 | } else if ([@"sftpMkdir" isEqualToString:call.method]) { 57 | [self sftpMkdir:args[@"path"] withKey:args[@"id"] result:result]; 58 | } else if ([@"sftpRm" isEqualToString:call.method]) { 59 | [self sftpRm:call.arguments[@"path"] withKey:args[@"id"] result:result]; 60 | } else if ([@"sftpRmdir" isEqualToString:call.method]) { 61 | [self sftpRmdir:args[@"path"] withKey:args[@"id"] result:result]; 62 | } else if ([@"sftpDownload" isEqualToString:call.method]) { 63 | [self sftpDownload:args[@"path"] toPath:args[@"toPath"] withKey:args[@"id"] result:result]; 64 | } else if ([@"sftpCancelDownload" isEqualToString:call.method]) { 65 | [self sftpCancelDownload:args[@"id"]]; 66 | } else if ([@"sftpUpload" isEqualToString:call.method]) { 67 | [self sftpUpload:call.arguments[@"path"] 68 | toPath:call.arguments[@"toPath"] 69 | withKey:args[@"id"] result:result]; 70 | } else if ([@"sftpCancelUpload" isEqualToString:call.method]) { 71 | [self sftpCancelUpload:args[@"id"]]; 72 | } else if ([@"disconnectSFTP" isEqualToString:call.method]) { 73 | [self disconnectSFTP:args[@"id"]]; 74 | } else if ([@"disconnect" isEqualToString:call.method]) { 75 | [self disconnect:args[@"id"]]; 76 | } else { 77 | result(FlutterMethodNotImplemented); 78 | } 79 | } 80 | 81 | - (NSMutableDictionary*) clientPool { 82 | if (!_clientPool) { 83 | _clientPool = [NSMutableDictionary new]; 84 | } 85 | return _clientPool; 86 | } 87 | 88 | - (SSHClient*) clientForKey:(nonnull NSString*)key { 89 | return [[self clientPool] objectForKey:key]; 90 | } 91 | 92 | - (BOOL)isConnected:(NMSSHSession *)session 93 | result:(FlutterResult)result { 94 | if (session && session.isConnected && session.isAuthorized) { 95 | return true; 96 | } else { 97 | NSLog(@"Session not connected"); 98 | result([FlutterError errorWithCode:@"connection_failure" message:@"No connected session" details:nil]); 99 | return false; 100 | } 101 | } 102 | 103 | - (BOOL)isSFTPConnected:(NMSFTP *)sftpSesion 104 | result:(FlutterResult)result { 105 | if (sftpSesion) { 106 | return true; 107 | } else { 108 | NSLog(@"SFTP not connected"); 109 | result([FlutterError errorWithCode:@"sftp_failure" message:@"No sftp connection" details:nil]); 110 | return false; 111 | } 112 | } 113 | 114 | - (void)connectToHost:(NSString *)host 115 | port:(int)port 116 | withUsername:(NSString *)username 117 | passwordOrKey:(id) passwordOrKey // password or {privateKey: value, [publicKey: value, passphrase: value]} 118 | withKey:(nonnull NSString*)key 119 | result:(FlutterResult)result { 120 | NMSSHSession* session = [NMSSHSession connectToHost:host 121 | port:port 122 | withUsername:username]; 123 | if (session && session.isConnected) { 124 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 125 | if ([passwordOrKey isKindOfClass:[NSString class]]) 126 | [session authenticateByPassword:passwordOrKey]; 127 | else 128 | [session authenticateByInMemoryPublicKey:[passwordOrKey objectForKey:@"publicKey"] 129 | privateKey:[passwordOrKey objectForKey:@"privateKey"] 130 | andPassword:[passwordOrKey objectForKey:@"passphrase"]]; 131 | 132 | if (session.isAuthorized) { 133 | SSHClient* client = [[SSHClient alloc] init]; 134 | client._session = session; 135 | client._key = key; 136 | [[self clientPool] setObject:client forKey:key]; 137 | NSLog(@"Session connected"); 138 | result(@"session_connected"); 139 | } else { 140 | NSLog(@"Authentication failed"); 141 | result([FlutterError errorWithCode:@"auth_failure" 142 | message:[NSString stringWithFormat:@"Authentication to host %@ failed", host] 143 | details:nil]); 144 | } 145 | }); 146 | } else { 147 | NSLog(@"Connection to host %@ failed", host); 148 | result([FlutterError errorWithCode:@"connection_failure" 149 | message:[NSString stringWithFormat:@"Connection to host %@ failed", host] 150 | details:nil]); 151 | } 152 | } 153 | 154 | - (void) execute:(NSString *)command 155 | withKey:(nonnull NSString*)key 156 | result:(FlutterResult)result { 157 | SSHClient* client = [self clientForKey:key]; 158 | if (client) { 159 | NMSSHSession* session = client._session; 160 | if ([self isConnected:session result:result]) { 161 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 162 | NSError* error = nil; 163 | NSString* response = [session.channel execute:command error:&error timeout:@10]; 164 | if (error) { 165 | NSLog(@"Error executing command: %@", error); 166 | result([error flutterError]); 167 | } else { 168 | result(response); 169 | } 170 | }); 171 | } 172 | } else { 173 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 174 | } 175 | } 176 | 177 | - (void) startShell:(nonnull NSString*)key 178 | ptyType:(NSString *)ptyType // vanilla, vt100, vt102, vt220, ansi, xterm 179 | result:(FlutterResult)result { 180 | [self closeShell:key]; 181 | SSHClient* client = [self clientForKey:key]; 182 | if (client) { 183 | client.delegate = self; 184 | __block NSError *error = nil; 185 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 186 | [client startShell:ptyType error:&error]; 187 | if (error) { 188 | NSLog(@"Error starting shell: %@", error); 189 | result([error flutterError]); 190 | } else { 191 | NSLog(@"Shell started"); 192 | result(@"shell_started"); 193 | } 194 | }); 195 | } else { 196 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 197 | } 198 | } 199 | 200 | - (void) closeShell:(nonnull NSString*)key { 201 | SSHClient* client = [self clientForKey:key]; 202 | if (client && client._session && client._session.channel) { 203 | [client._session.channel closeShell]; 204 | } 205 | } 206 | 207 | - (void) writeToShell:(NSString *)command 208 | withKey:(nonnull NSString*)key 209 | result:(FlutterResult)result { 210 | SSHClient* client = [self clientForKey:key]; 211 | if (client) { 212 | if ([self isConnected:client._session result:result]) { 213 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 214 | NSError* error = nil; 215 | [client._session.channel write:command error:&error timeout:@10]; 216 | if (error) { 217 | NSLog(@"Error writing to shell: %@", error); 218 | result([error flutterError]); 219 | } else { 220 | NSLog(@"Write success"); 221 | result(@"write_success"); 222 | } 223 | }); 224 | } 225 | } else { 226 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 227 | } 228 | } 229 | 230 | - (void) connectSFTP:(nonnull NSString*)key 231 | result:(FlutterResult)result { 232 | SSHClient* client = [self clientForKey:key]; 233 | if (client) { 234 | if ([self isConnected:client._session result:result]) { 235 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 236 | NMSFTP* sftpSession = [NMSFTP connectWithSession:client._session]; 237 | if (sftpSession) { 238 | client._sftpSession = sftpSession; 239 | NSLog(@"SFTP connected"); 240 | result(@"sftp_connected"); 241 | } else { 242 | result([FlutterError errorWithCode:@"sftp_failure" message:@"Failed to connect SFTP" details:nil]); 243 | } 244 | }); 245 | } 246 | } else { 247 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 248 | } 249 | } 250 | 251 | - (void) sftpLs:(NSString *)path 252 | withKey:(nonnull NSString*)key 253 | result:(FlutterResult)result { 254 | SSHClient* client = [self clientForKey:key]; 255 | if (client) { 256 | if ([self isConnected:client._session result:result] && 257 | [self isSFTPConnected:client._sftpSession result:result]) { 258 | 259 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 260 | NSArray* fileList = [client._sftpSession contentsOfDirectoryAtPath:path]; 261 | if (fileList) { 262 | NSMutableArray* array = [NSMutableArray array]; 263 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 264 | [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; 265 | for (NMSFTPFile* file in fileList) { 266 | NSMutableDictionary* res = [NSMutableDictionary dictionary]; 267 | [res setObject:file.filename forKey:@"filename"]; 268 | [res setObject:[NSNumber numberWithBool:file.isDirectory] forKey:@"isDirectory"]; 269 | [res setObject:[formatter stringFromDate:file.modificationDate] forKey:@"modificationDate"]; 270 | [res setObject:[formatter stringFromDate:file.lastAccess] forKey:@"lastAccess"]; 271 | [res setObject:file.fileSize forKey:@"fileSize"]; 272 | [res setObject:[NSNumber numberWithUnsignedLong:file.ownerUserID] forKey:@"ownerUserID"]; 273 | [res setObject:[NSNumber numberWithUnsignedLong:file.ownerGroupID] forKey:@"ownerGroupID"]; 274 | [res setObject:file.permissions forKey:@"permissions"]; 275 | [res setObject:[NSNumber numberWithUnsignedLong:file.flags] forKey:@"flags"]; 276 | [array addObject:res]; 277 | } 278 | result(array); 279 | } else { 280 | result([FlutterError errorWithCode:@"ls_failure" 281 | message:[NSString stringWithFormat:@"Failed to list path %@",path] 282 | details:nil]); 283 | } 284 | }); 285 | } else { 286 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 287 | } 288 | } 289 | } 290 | 291 | - (void) sftpRename:(NSString *)oldPath 292 | newPath:(NSString *)newPath 293 | withKey:(nonnull NSString*)key 294 | result:(FlutterResult)result { 295 | SSHClient* client = [self clientForKey:key]; 296 | if (client) { 297 | if ([self isConnected:client._session result:result] && 298 | [self isSFTPConnected:client._sftpSession result:result]) { 299 | if ([client._sftpSession moveItemAtPath:oldPath toPath:newPath]) { 300 | NSLog(@"rename success"); 301 | result(@"rename_success"); 302 | } else { 303 | result([FlutterError errorWithCode:@"rename_failure" 304 | message:[NSString stringWithFormat:@"Failed to rename path %@ to %@", oldPath, newPath] 305 | details:nil]); 306 | } 307 | } 308 | } else { 309 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 310 | } 311 | } 312 | 313 | - (void) sftpMkdir:(NSString *)path 314 | withKey:(nonnull NSString*)key 315 | result:(FlutterResult)result { 316 | SSHClient* client = [self clientForKey:key]; 317 | if (client) { 318 | if ([self isConnected:client._session result:result] && 319 | [self isSFTPConnected:client._sftpSession result:result]) { 320 | if([client._sftpSession createDirectoryAtPath:path]) { 321 | NSLog(@"mkdir success"); 322 | result(@"mkdir_success"); 323 | } else { 324 | result([FlutterError errorWithCode:@"mkdir_failure" 325 | message:[NSString stringWithFormat:@"Failed to create directory %@", path] 326 | details:nil]); 327 | } 328 | } 329 | } else { 330 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 331 | } 332 | } 333 | 334 | - (void) sftpRm:(NSString *)path 335 | withKey:(nonnull NSString*)key 336 | result:(FlutterResult)result { 337 | SSHClient* client = [self clientForKey:key]; 338 | if (client) { 339 | if ([self isConnected:client._session result:result] && 340 | [self isSFTPConnected:client._sftpSession result:result]) { 341 | if([client._sftpSession removeFileAtPath:path]) { 342 | NSLog(@"rm success"); 343 | result(@"rm_success"); 344 | } else { 345 | result([FlutterError errorWithCode:@"rm_failure" 346 | message:[NSString stringWithFormat:@"Failed to remove %@", path] 347 | details:nil]); 348 | } 349 | } 350 | } else { 351 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 352 | } 353 | } 354 | 355 | - (void) sftpRmdir:(NSString *)path 356 | withKey:(nonnull NSString*)key 357 | result:(FlutterResult)result { 358 | SSHClient* client = [self clientForKey:key]; 359 | if (client) { 360 | if ([self isConnected:client._session result:result] && 361 | [self isSFTPConnected:client._sftpSession result:result]) { 362 | if([client._sftpSession removeDirectoryAtPath:path]) { 363 | NSLog(@"rmdir success"); 364 | result(@"rmdir_success"); 365 | } else { 366 | result([FlutterError errorWithCode:@"rmdir_failure" 367 | message:[NSString stringWithFormat:@"Failed to remove %@", path] 368 | details:nil]); 369 | } 370 | } 371 | } else { 372 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 373 | } 374 | } 375 | 376 | - (void) sftpDownload:(NSString *)path 377 | toPath:(NSString *)toPath 378 | withKey:(nonnull NSString*)key 379 | result:(FlutterResult)result{ 380 | SSHClient* client = [self clientForKey:key]; 381 | if (client) { 382 | if ([self isConnected:client._session result:result] && 383 | [self isSFTPConnected:client._sftpSession result:result]) { 384 | NSString* filePath = [NSString stringWithFormat:@"%@/%@", toPath, [path lastPathComponent]]; 385 | 386 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 387 | client.delegate = self; 388 | NSError* error = nil; 389 | [client sftpDownload:path toPath:filePath error:&error]; 390 | if (error) { 391 | result([error flutterError]); 392 | } else if (client._downloadContinue) { 393 | result(filePath); 394 | } else { 395 | result(@"download_canceled"); 396 | } 397 | }); 398 | } 399 | } else { 400 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 401 | } 402 | } 403 | 404 | - (void) sftpUpload:(NSString *)filePath 405 | toPath:(NSString *)toPath 406 | withKey:(nonnull NSString*)key 407 | result:(FlutterResult)result { 408 | SSHClient* client = [self clientForKey:key]; 409 | if (client) { 410 | if ([self isConnected:client._session result:result] && 411 | [self isSFTPConnected:client._sftpSession result:result]) { 412 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 413 | client.delegate = self; 414 | BOOL res = [client sftpUpload:filePath toPath:toPath]; 415 | if (res) { 416 | result(@"upload_success"); 417 | } else { 418 | if (client._uploadContinue) { 419 | NSLog(@"Error uploading file"); 420 | result([FlutterError errorWithCode:@"upload_failure" 421 | message:[NSString stringWithFormat:@"Failed to upload %@ to %@", filePath, toPath] 422 | details:nil]); 423 | } else { 424 | result(@"upload_canceled"); 425 | } 426 | } 427 | }); 428 | } 429 | } else { 430 | result([FlutterError errorWithCode:@"unknown_client" message:@"Unknown client" details:nil]); 431 | } 432 | } 433 | 434 | - (void) sftpCancelDownload:(nonnull NSString*)key { 435 | SSHClient* client = [self clientForKey:key]; 436 | if (client) { 437 | client._downloadContinue = false; 438 | } 439 | } 440 | 441 | - (void) sftpCancelUpload:(nonnull NSString*)key { 442 | SSHClient* client = [self clientForKey:key]; 443 | if (client) { 444 | client._uploadContinue = false; 445 | } 446 | } 447 | 448 | - (void) disconnectSFTP:(nonnull NSString*)key { 449 | SSHClient* client = [self clientForKey:key]; 450 | if (client && client._sftpSession) { 451 | [client._sftpSession disconnect]; 452 | } 453 | } 454 | 455 | - (void) disconnect:(nonnull NSString*)key { 456 | [self closeShell:key]; 457 | [self disconnectSFTP:key]; 458 | SSHClient* client = [self clientForKey:key]; 459 | if (client && client._session) { 460 | [client._session disconnect]; 461 | } 462 | } 463 | 464 | - (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink { 465 | _eventSink = eventSink; 466 | return nil; 467 | } 468 | 469 | - (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments { 470 | _eventSink = nil; 471 | return nil; 472 | } 473 | 474 | - (void) shellEvent:(NSString *)event withKey:(NSString *)key { 475 | if (!_eventSink) return; 476 | _eventSink(@{@"name": @"Shell", @"key": key, @"value": event}); 477 | } 478 | 479 | - (void) downloadProgressEvent:(int)event withKey:(NSString *)key { 480 | if (!_eventSink) return; 481 | _eventSink(@{@"name": @"DownloadProgress", @"key": key, @"value": [NSNumber numberWithInt:event]}); 482 | } 483 | 484 | - (void)uploadProgressEvent:(int)event withKey:(NSString *)key { 485 | if (!_eventSink) return; 486 | _eventSink(@{@"name": @"UploadProgress", @"key": key, @"value": [NSNumber numberWithInt:event]}); 487 | } 488 | 489 | @end 490 | -------------------------------------------------------------------------------- /ios/ssh.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'ssh' 6 | s.version = '0.0.7' 7 | s.summary = 'SSH and SFTP client for Flutter.' 8 | s.description = <<-DESC 9 | SSH and SFTP client for Flutter. Wraps iOS library NMSSH and Android library Jsch. 10 | DESC 11 | s.homepage = 'https://github.com/shaqian/flutter_ssh' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Qian Sha' => 'https://github.com/shaqian' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.dependency 'NMSSH' 19 | 20 | s.ios.deployment_target = '8.0' 21 | end 22 | 23 | -------------------------------------------------------------------------------- /lib/ssh.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:meta/meta.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | const MethodChannel _channel = const MethodChannel('ssh'); 7 | const EventChannel _eventChannel = const EventChannel('shell_sftp'); 8 | Stream _onStateChanged; 9 | 10 | Stream get onStateChanged { 11 | if (_onStateChanged == null) { 12 | _onStateChanged = 13 | _eventChannel.receiveBroadcastStream().map((dynamic event) => event); 14 | } 15 | return _onStateChanged; 16 | } 17 | 18 | typedef void Callback(dynamic result); 19 | 20 | class SSHClient { 21 | String id; 22 | String host; 23 | int port; 24 | String username; 25 | dynamic passwordOrKey; 26 | StreamSubscription stateSubscription; 27 | Callback shellCallback; 28 | Callback uploadCallback; 29 | Callback downloadCallback; 30 | 31 | SSHClient({ 32 | @required this.host, 33 | @required this.port, 34 | @required this.username, 35 | @required 36 | this.passwordOrKey, // password or {privateKey: value, [publicKey: value, passphrase: value]} 37 | }) { 38 | var uuid = new Uuid(); 39 | id = uuid.v4(); 40 | stateSubscription = onStateChanged.listen((dynamic result) { 41 | _parseOutput(result); 42 | }); 43 | } 44 | 45 | _parseOutput(dynamic result) { 46 | switch (result["name"]) { 47 | case "Shell": 48 | if (shellCallback != null && result["key"] == id) 49 | shellCallback(result["value"]); 50 | break; 51 | case "DownloadProgress": 52 | if (downloadCallback != null && result["key"] == id) 53 | downloadCallback(result["value"]); 54 | break; 55 | case "UploadProgress": 56 | if (uploadCallback != null && result["key"] == id) 57 | uploadCallback(result["value"]); 58 | break; 59 | } 60 | } 61 | 62 | Future connect() async { 63 | var result = await _channel.invokeMethod('connectToHost', { 64 | "id": id, 65 | "host": host, 66 | "port": port, 67 | "username": username, 68 | "passwordOrKey": passwordOrKey, 69 | }); 70 | return result; 71 | } 72 | 73 | Future execute(String cmd) async { 74 | var result = await _channel.invokeMethod('execute', { 75 | "id": id, 76 | "cmd": cmd, 77 | }); 78 | return result; 79 | } 80 | 81 | Future portForwardL(int rport, int lport, String rhost) async { 82 | var result = await _channel.invokeMethod('portForwardL', { 83 | "id": id, 84 | "rhost": rhost, 85 | "rport": rport, 86 | "lport": lport 87 | }); 88 | return result; 89 | } 90 | 91 | Future startShell({ 92 | String ptyType = "vanilla", // vanilla, vt100, vt102, vt220, ansi, xterm 93 | Callback callback, 94 | }) async { 95 | shellCallback = callback; 96 | var result = await _channel.invokeMethod('startShell', { 97 | "id": id, 98 | "ptyType": ptyType, 99 | }); 100 | return result; 101 | } 102 | 103 | Future writeToShell(String cmd) async { 104 | var result = await _channel.invokeMethod('writeToShell', { 105 | "id": id, 106 | "cmd": cmd, 107 | }); 108 | return result; 109 | } 110 | 111 | Future closeShell() async { 112 | shellCallback = null; 113 | await _channel.invokeMethod('closeShell', { 114 | "id": id, 115 | }); 116 | } 117 | 118 | Future connectSFTP() async { 119 | var result = await _channel.invokeMethod('connectSFTP', { 120 | "id": id, 121 | }); 122 | return result; 123 | } 124 | 125 | Future sftpLs([String path = '.']) async { 126 | var result = await _channel.invokeMethod('sftpLs', { 127 | "id": id, 128 | "path": path, 129 | }); 130 | return result; 131 | } 132 | 133 | Future sftpRename({ 134 | @required String oldPath, 135 | @required String newPath, 136 | }) async { 137 | var result = await _channel.invokeMethod('sftpRename', { 138 | "id": id, 139 | "oldPath": oldPath, 140 | "newPath": newPath, 141 | }); 142 | return result; 143 | } 144 | 145 | Future sftpMkdir(String path) async { 146 | var result = await _channel.invokeMethod('sftpMkdir', { 147 | "id": id, 148 | "path": path, 149 | }); 150 | return result; 151 | } 152 | 153 | Future sftpRm(String path) async { 154 | var result = await _channel.invokeMethod('sftpRm', { 155 | "id": id, 156 | "path": path, 157 | }); 158 | return result; 159 | } 160 | 161 | Future sftpRmdir(String path) async { 162 | var result = await _channel.invokeMethod('sftpRmdir', { 163 | "id": id, 164 | "path": path, 165 | }); 166 | return result; 167 | } 168 | 169 | Future sftpDownload({ 170 | @required String path, 171 | @required String toPath, 172 | Callback callback, 173 | }) async { 174 | downloadCallback = callback; 175 | var result = await _channel.invokeMethod('sftpDownload', { 176 | "id": id, 177 | "path": path, 178 | "toPath": toPath, 179 | }); 180 | return result; 181 | } 182 | 183 | Future sftpCancelDownload() async { 184 | await _channel.invokeMethod('sftpCancelDownload', { 185 | "id": id, 186 | }); 187 | } 188 | 189 | Future sftpUpload({ 190 | @required String path, 191 | @required String toPath, 192 | Callback callback, 193 | }) async { 194 | uploadCallback = callback; 195 | var result = await _channel.invokeMethod('sftpUpload', { 196 | "id": id, 197 | "path": path, 198 | "toPath": toPath, 199 | }); 200 | return result; 201 | } 202 | 203 | Future sftpCancelUpload() async { 204 | await _channel.invokeMethod('sftpCancelUpload', { 205 | "id": id, 206 | }); 207 | } 208 | 209 | Future disconnectSFTP() async { 210 | uploadCallback = null; 211 | downloadCallback = null; 212 | await _channel.invokeMethod('disconnectSFTP', { 213 | "id": id, 214 | }); 215 | } 216 | 217 | disconnect() { 218 | shellCallback = null; 219 | uploadCallback = null; 220 | downloadCallback = null; 221 | stateSubscription.cancel(); 222 | _channel.invokeMethod('disconnect', { 223 | "id": id, 224 | }); 225 | } 226 | 227 | Future isConnected() async { 228 | bool connected = false; // default to false 229 | var result = await _channel.invokeMethod('isConnected', {"id": id,}); 230 | if (result == "true") { // results returns a string, therefor we need to check the string 'true' 231 | connected = true; 232 | } 233 | return connected; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: ssh 2 | description: SSH and SFTP client for Flutter. Wraps iOS library NMSSH and Android library JSch. 3 | version: 0.0.7 4 | homepage: https://github.com/shaqian/flutter_ssh 5 | 6 | environment: 7 | sdk: ">=2.1.0 <3.0.0" 8 | flutter: ">=1.10.0 <2.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | meta: ^1.1.8 15 | uuid: ^2.0.0 16 | 17 | # For information on the generic Dart part of this file, see the 18 | # following page: https://www.dartlang.org/tools/pub/pubspec 19 | 20 | # The following section is specific to Flutter. 21 | flutter: 22 | plugin: 23 | platforms: 24 | android: 25 | package: sq.flutter.ssh 26 | pluginClass: SshPlugin 27 | ios: 28 | pluginClass: SshPlugin 29 | 30 | # To add assets to your plugin package, add an assets section, like this: 31 | # assets: 32 | # - images/a_dot_burr.jpeg 33 | # - images/a_dot_ham.jpeg 34 | # 35 | # For details regarding assets in packages, see 36 | # https://flutter.io/assets-and-images/#from-packages 37 | # 38 | # An image asset can refer to one or more resolution-specific "variants", see 39 | # https://flutter.io/assets-and-images/#resolution-aware. 40 | 41 | # To add custom fonts to your plugin package, add a fonts section here, 42 | # in this "flutter" section. Each entry in this list should have a 43 | # "family" key with the font family name, and a "fonts" key with a 44 | # list giving the asset and other descriptors for the font. For 45 | # example: 46 | # fonts: 47 | # - family: Schyler 48 | # fonts: 49 | # - asset: fonts/Schyler-Regular.ttf 50 | # - asset: fonts/Schyler-Italic.ttf 51 | # style: italic 52 | # - family: Trajan Pro 53 | # fonts: 54 | # - asset: fonts/TrajanPro.ttf 55 | # - asset: fonts/TrajanPro_Bold.ttf 56 | # weight: 700 57 | # 58 | # For details regarding fonts in packages, see 59 | # https://flutter.io/custom-fonts/#from-packages 60 | -------------------------------------------------------------------------------- /ssh.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /ssh_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | --------------------------------------------------------------------------------