diff --git a/.gitignore b/.gitignore
index b6ba85a6d787ff14d6e67e403f8b2169a3b2b98d..a166d2277e213d6165f948d2d7b636193da94cbe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,9 @@
-*.iml
-.gradle
-build
-__MACOSX
-.idea
-keystore
-.DS_Store
-local.properties
-*.apk
+*.iml
+.gradle
+build
+__MACOSX
+.idea
+keystore
+.DS_Store
+local.properties
+*.apk
diff --git a/LICENSE b/LICENSE
index c1d636e0556d0d91a1ac3c1597d81abbdc18902d..cd7b044d781ba211ab922342ab6fe10af17dee0d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,201 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software code code, documentation
- code, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, code code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- from_content syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright {yyyy} {name of copyright owner}
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software code code, documentation
+ code, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, code code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ from_content syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
index 5e5ae87aafe86a9b55e5423a45a4b260474eb7e8..6a56563f891f1731e4c863c511706615074ccf84 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# About
-QFtplib provides basic ftp features for Android project.
-
-It is not a standalone project, it will be included by QPython / QPython3 as a submodule
+# About
+QFtplib provides basic ftp features for Android project.
+
+It is not a standalone project, it will be included by QPython / QPython3 as a submodule
diff --git a/build.gradle b/build.gradle
index cdbb42571a887ca1f48499de61cdab0f7d2d2c30..c2949f86d4623c5927ac12d95303a0dfdb17fbc0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,18 +1,25 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion rootProject.ext.compileSdkVersion
- buildToolsVersion rootProject.ext.buildToolsVersion
-
- defaultConfig {
- minSdkVersion rootProject.ext.minSdkVersion
- targetSdkVersion rootProject.ext.targetSdkVersion
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
- }
- }
-}
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion rootProject.ext.compileSdkVersion
+
+ defaultConfig {
+ minSdkVersion rootProject.ext.minSdkVersion
+ targetSdkVersion rootProject.ext.targetSdkVersion
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ dependencies {
+ api 'com.android.support:documentfile:28.0.0'
+ }
+}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index cb375251af9b4667a123f26b27b7c34eb5a09711..c3b0df8761046393244876db727f53f1448f54cb 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -1,8 +1,8 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/main/java/org/swiftp/Defaults.java b/src/main/java/org/swiftp/Defaults.java
index 68884b98f3441b6fac39b7d7ca2244e18b295942..bf8b141c44fbdd7379f92c76a5739f3404f18c49 100644
--- a/src/main/java/org/swiftp/Defaults.java
+++ b/src/main/java/org/swiftp/Defaults.java
@@ -1,153 +1,153 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp;
-
-import android.content.Context;
-import android.util.Log;
-
-public class Defaults {
- protected static int inputBufferSize = 256;
- public static int dataChunkSize = 65536; // do file I/O in 64k chunks
- protected static int sessionMonitorScrollBack = 10;
- protected static int serverLogScrollBack = 10;
- protected static int uiLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
- protected static int consoleLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
- protected static String settingsName = "SwiFTP";
- //protected static String username = "user";
- //protected static String password = "";
- protected static int portNumber = 2121;
-// protected static int ipRetrievalAttempts = 5;
- public static final int tcpConnectionBacklog = 5;
- public static final String chrootDir = "/";
- public static final boolean acceptWifi = true;
- public static final boolean acceptNet = false; // don't incur bandwidth charges
- public static final boolean stayAwake = true;
- public static final int REMOTE_PROXY_PORT = 2222;
- public static final String STRING_ENCODING = "UTF-8";
- public static final int SO_TIMEOUT_MS = 30000; // socket timeout millis
- // FTP control sessions should start out in ASCII, according to the RFC.
- // However, many clients don't turn on UTF-8 even though they support it,
- // so we just turn it on by default.
- public static final String SESSION_ENCODING = "UTF-8";
-
- // This is a flag that should be true for public builds and false for dev builds
- public static final boolean release = true;
-
- // Try to fix the transfer stall bug, reopen the destination file periodically
- //public static final boolean do_reopen_hack = false;
- //public static final int bytes_between_reopen = 4000000;
-
- // Try to fix the transfer stall bug, flush the file periodically
- //public static final boolean do_flush_hack = false;
- //public static final int bytes_between_flush = 500000;
-
- public static final boolean do_mediascanner_notify = true;
-
-
-// public static int getIpRetrievalAttempts() {
-// return ipRetrievalAttempts;
-// }
-
-// public static void setIpRetrievalAttempts(int ipRetrievalAttempts) {
-// Defaults.ipRetrievalAttempts = ipRetrievalAttempts;
-// }
-
- public static int getPortNumber() {
- return portNumber;
- }
-
- public static void setPortNumber(int portNumber) {
- Defaults.portNumber = portNumber;
- }
-
- public static String getSettingsName() {
- return settingsName;
- }
-
- public static void setSettingsName(String settingsName) {
- Defaults.settingsName = settingsName;
- }
-
- public static int getSettingsMode() {
- return settingsMode;
- }
-
- public static void setSettingsMode(int settingsMode) {
- Defaults.settingsMode = settingsMode;
- }
-
- public static void setServerLogScrollBack(int serverLogScrollBack) {
- Defaults.serverLogScrollBack = serverLogScrollBack;
- }
-
- protected static int settingsMode = Context.MODE_WORLD_WRITEABLE;
-
- public static int getUiLogLevel() {
- return uiLogLevel;
- }
-
- public static void setUiLogLevel(int uiLogLevel) {
- Defaults.uiLogLevel = uiLogLevel;
- }
-
- public static int getInputBufferSize() {
- return inputBufferSize;
- }
-
- public static void setInputBufferSize(int inputBufferSize) {
- Defaults.inputBufferSize = inputBufferSize;
- }
-
- public static int getDataChunkSize() {
- return dataChunkSize;
- }
-
- public static void setDataChunkSize(int dataChunkSize) {
- Defaults.dataChunkSize = dataChunkSize;
- }
-
- public static int getSessionMonitorScrollBack() {
- return sessionMonitorScrollBack;
- }
-
- public static void setSessionMonitorScrollBack(
- int sessionMonitorScrollBack)
- {
- Defaults.sessionMonitorScrollBack = sessionMonitorScrollBack;
- }
-
- public static int getServerLogScrollBack() {
- return serverLogScrollBack;
- }
-
- public static void setLogScrollBack(int serverLogScrollBack) {
- Defaults.serverLogScrollBack = serverLogScrollBack;
- }
-
- public static int getConsoleLogLevel() {
- return consoleLogLevel;
- }
-
- public static void setConsoleLogLevel(int consoleLogLevel) {
- Defaults.consoleLogLevel = consoleLogLevel;
- }
-
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp;
+
+import android.content.Context;
+import android.util.Log;
+
+public class Defaults {
+ protected static int inputBufferSize = 256;
+ public static int dataChunkSize = 65536; // do file I/O in 64k chunks
+ protected static int sessionMonitorScrollBack = 10;
+ protected static int serverLogScrollBack = 10;
+ protected static int uiLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
+ protected static int consoleLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
+ protected static String settingsName = "SwiFTP";
+ //protected static String username = "user";
+ //protected static String password = "";
+ protected static int portNumber = 0;//2121;
+// protected static int ipRetrievalAttempts = 5;
+ public static final int tcpConnectionBacklog = 5;
+ public static final String chrootDir = "/";
+ public static final boolean acceptWifi = true;
+ public static final boolean acceptNet = false; // don't incur bandwidth charges
+ public static final boolean stayAwake = true;
+ public static final int REMOTE_PROXY_PORT = 2222;
+ public static final String STRING_ENCODING = "UTF-8";
+ public static final int SO_TIMEOUT_MS = 30000; // socket timeout millis
+ // FTP control sessions should start out in ASCII, according to the RFC.
+ // However, many clients don't turn on UTF-8 even though they support it,
+ // so we just turn it on by default.
+ public static final String SESSION_ENCODING = "UTF-8";
+
+ // This is a flag that should be true for public builds and false for dev builds
+ public static final boolean release = true;
+
+ // Try to fix the transfer stall bug, reopen the destination file periodically
+ //public static final boolean do_reopen_hack = false;
+ //public static final int bytes_between_reopen = 4000000;
+
+ // Try to fix the transfer stall bug, flush the file periodically
+ //public static final boolean do_flush_hack = false;
+ //public static final int bytes_between_flush = 500000;
+
+ public static final boolean do_mediascanner_notify = true;
+
+
+// public static int getIpRetrievalAttempts() {
+// return ipRetrievalAttempts;
+// }
+
+// public static void setIpRetrievalAttempts(int ipRetrievalAttempts) {
+// Defaults.ipRetrievalAttempts = ipRetrievalAttempts;
+// }
+
+ public static int getPortNumber() {
+ return portNumber;
+ }
+
+ public static void setPortNumber(int portNumber) {
+ Defaults.portNumber = portNumber;
+ }
+
+ public static String getSettingsName() {
+ return settingsName;
+ }
+
+ public static void setSettingsName(String settingsName) {
+ Defaults.settingsName = settingsName;
+ }
+
+ public static int getSettingsMode() {
+ return settingsMode;
+ }
+
+ public static void setSettingsMode(int settingsMode) {
+ Defaults.settingsMode = settingsMode;
+ }
+
+ public static void setServerLogScrollBack(int serverLogScrollBack) {
+ Defaults.serverLogScrollBack = serverLogScrollBack;
+ }
+
+ protected static int settingsMode = Context.MODE_WORLD_WRITEABLE;
+
+ public static int getUiLogLevel() {
+ return uiLogLevel;
+ }
+
+ public static void setUiLogLevel(int uiLogLevel) {
+ Defaults.uiLogLevel = uiLogLevel;
+ }
+
+ public static int getInputBufferSize() {
+ return inputBufferSize;
+ }
+
+ public static void setInputBufferSize(int inputBufferSize) {
+ Defaults.inputBufferSize = inputBufferSize;
+ }
+
+ public static int getDataChunkSize() {
+ return dataChunkSize;
+ }
+
+ public static void setDataChunkSize(int dataChunkSize) {
+ Defaults.dataChunkSize = dataChunkSize;
+ }
+
+ public static int getSessionMonitorScrollBack() {
+ return sessionMonitorScrollBack;
+ }
+
+ public static void setSessionMonitorScrollBack(
+ int sessionMonitorScrollBack)
+ {
+ Defaults.sessionMonitorScrollBack = sessionMonitorScrollBack;
+ }
+
+ public static int getServerLogScrollBack() {
+ return serverLogScrollBack;
+ }
+
+ public static void setLogScrollBack(int serverLogScrollBack) {
+ Defaults.serverLogScrollBack = serverLogScrollBack;
+ }
+
+ public static int getConsoleLogLevel() {
+ return consoleLogLevel;
+ }
+
+ public static void setConsoleLogLevel(int consoleLogLevel) {
+ Defaults.consoleLogLevel = consoleLogLevel;
+ }
+
+
+}
diff --git a/src/main/java/org/swiftp/FTPServerService.java b/src/main/java/org/swiftp/FTPServerService.java
index e688392395b0058ff6a973f6d6500585e8d9687f..c2cfd59685370f77ca5f24d1e3f05cec23be9a0a 100644
--- a/src/main/java/org/swiftp/FTPServerService.java
+++ b/src/main/java/org/swiftp/FTPServerService.java
@@ -1,705 +1,720 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
- */
-
-package org.swiftp;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.NetworkInterface;
-import java.net.ServerSocket;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import org.swiftp.server.ProxyConnector;
-import org.swiftp.server.SessionThread;
-import org.swiftp.server.TcpListener;
-
-import android.annotation.SuppressLint;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.graphics.Bitmap;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.WifiLock;
-import android.os.Build;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-public abstract class FTPServerService extends Service implements Runnable {
-
- // Service will broadcast (LocalBroadcast) when server start/stop
- static public final String ACTION_STARTED = "org.swiftp.FTPServerService.STARTED";
- static public final String ACTION_STOPPED = "org.swiftp.FTPServerService.STOPPED";
- static public final String ACTION_FAILEDTOSTART = "org.swiftp.FTPServerService.FAILEDTOSTART";
- public static final int BACKLOG = 21;
- public static final int MAX_SESSIONS = 5;
- public static final String WAKE_LOCK_TAG = "SwiFTP";
- // The server thread will check this often to look for incoming
- // connections. We are forced to use non-blocking accept() and polling
- // because we cannot wait forever in accept() if we want to be able
- // to receive an exit signal and cleanly exit.
- public static final int WAKE_INTERVAL_MS = 1000; // milliseconds
- private static final int WIFI_AP_STATE_ENABLED = 13;
- protected static Thread serverThread = null;
- protected static MyLog staticLog = new MyLog(FTPServerService.class.getName());
- protected static WifiLock wifiLock = null;
- protected static List sessionMonitor = new ArrayList();
- protected static List serverLog = new ArrayList();
-
- // protected static InetAddress serverAddress = null;
- protected static int uiLogLevel = Defaults.getUiLogLevel();
- protected static int port;
- protected static boolean acceptWifi;
- protected static boolean acceptNet;
- protected static boolean fullWake;
- private static SharedPreferences settings = null;
- private final List sessionThreads = new ArrayList();
- protected boolean shouldExit = false;
- protected MyLog myLog = new MyLog(getClass().getName());
- // protected ServerSocketChannel wifiSocket;
- protected ServerSocket listenSocket;
- NotificationManager notificationMgr = null;
- PowerManager.WakeLock wakeLock;
- private TcpListener wifiListener = null;
- private ProxyConnector proxyConnector = null;
-
- public FTPServerService() {
- }
-
- public static boolean isRunning() {
- // return true if and only if a server Thread is running
- if (serverThread == null) {
- staticLog.l(Log.DEBUG, "Server is not running (null serverThread)");
- return false;
- }
- if (!serverThread.isAlive()) {
- staticLog.l(Log.DEBUG, "serverThread non-null but !isAlive()");
- } else {
- staticLog.l(Log.DEBUG, "Server is alive");
- }
- return true;
- }
-
- /**
- * Gets the IP address of the wifi connection.
- *
- * @return The integer IP address if wifi enabled, or null if not.
- */
- public static InetAddress getWifiIp() {
- Context myContext = Globals.getContext().getApplicationContext();
- if (myContext == null) {
- throw new NullPointerException("Global context is null");
- }
- WifiManager wifiMgr = (WifiManager) myContext.getSystemService(Context.WIFI_SERVICE);
- if (isWifiEnabled()) {
- int ipAsInt = wifiMgr.getConnectionInfo().getIpAddress();
- if (ipAsInt == 0) {
- return null;
- } else {
- return Util.intToInet(ipAsInt);
- }
- } else {
- return null;
- }
- }
-
- public static InetAddress getWifiAndApIp(){
- InetAddress ip = getWifiIp();
- if (ip==null){
- try {
- for (NetworkInterface intf : Collections.list(NetworkInterface.getNetworkInterfaces())) {
- if (intf.getName().equals("wlan0")){
- for (InetAddress addr : Collections.list(intf.getInetAddresses())) {
- if (!addr.isLoopbackAddress() && addr.getHostAddress().contains(".")){
- ip = addr;
- break;
- }
- }
- break;
- }
- }
- } catch (SocketException ignored) {
- }
- }
- return ip;
- }
-
- public static boolean isWifiEnabled() {
- Context myContext = Globals.getContext();
- if (myContext == null) {
- throw new NullPointerException("Global context is null");
- }
- @SuppressLint("WifiManagerLeak") WifiManager wifiMgr = (WifiManager) myContext
- .getSystemService(Context.WIFI_SERVICE);
- if (wifiMgr.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
- return true;
- } else {
- return false;
- }
- }
-
- public static boolean isWifiAndApEnabled(){
- Context myContext = Globals.getContext();
- if (myContext == null) {
- throw new NullPointerException("Global context is null");
- }
- @SuppressLint("WifiManagerLeak") WifiManager wifiMgr = (WifiManager) myContext
- .getSystemService(Context.WIFI_SERVICE);
- if (wifiMgr.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
- return true;
- } else {
- try {
- Method method = wifiMgr.getClass().getMethod("getWifiApState");
- return (Integer) method.invoke(wifiMgr) == WIFI_AP_STATE_ENABLED;
- } catch (Exception e){
- return false;
- }
- }
- }
-
- public static List getSessionMonitorContents() {
- return new ArrayList(sessionMonitor);
- }
-
- public static List getServerLogContents() {
- return new ArrayList(serverLog);
- }
-
- public static void log(int msgLevel, String s) {
- serverLog.add(s);
- int maxSize = Defaults.getServerLogScrollBack();
- while (serverLog.size()> maxSize) {
- serverLog.remove(0);
- }
- // updateClients();
- }
-
- public static void writeMonitor(boolean incoming, String s) {
- }
-
- public static int getPort() {
- return port;
- }
-
- public static void setPort(int port) {
- FTPServerService.port = port;
- }
-
- static public SharedPreferences getSettings() {
- return settings;
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- // We don't implement this functionality, so ignore it
- return null;
- }
-
- @Override
- public void onCreate() {
- myLog.l(Log.DEBUG, "SwiFTP server created");
- // Set the application-wide context global, if not already set
- Context myContext = Globals.getContext();
- if (myContext == null) {
- myContext = getApplicationContext();
- if (myContext != null) {
- Globals.setContext(myContext);
- }
- }
- }
-
- @Override
- public void onStart(Intent intent, int startId) {
- super.onStart(intent, startId);
-
- shouldExit = false;
- int attempts = 10;
- // The previous server thread may still be cleaning up, wait for it
- // to finish.
- while (serverThread != null) {
- myLog.l(Log.WARN, "Won't start, server thread exists");
- if (attempts> 0) {
- attempts--;
- Util.sleepIgnoreInterupt(1000);
- } else {
- myLog.l(Log.ERROR, "Server thread already exists");
- return;
- }
- }
- myLog.l(Log.DEBUG, "Creating server thread");
- serverThread = new Thread(this);
- serverThread.start();
- }
-
- @Override
- public void onDestroy() {
- myLog.l(Log.INFO, "onDestroy() Stopping server");
- shouldExit = true;
- if (serverThread == null) {
- myLog.l(Log.WARN, "Stopping with null serverThread");
- return;
- } else {
- serverThread.interrupt();
- try {
- serverThread.join(10000); // wait 10 sec for server thread to
- // finish
- } catch (InterruptedException e) {
- }
- if (serverThread.isAlive()) {
- myLog.l(Log.WARN, "Server thread failed to exit");
- // it may still exit eventually if we just leave the
- // shouldExit flag set
- } else {
- myLog.d("serverThread join()ed ok");
- serverThread = null;
- }
- }
- try {
- if (listenSocket != null) {
- myLog.l(Log.INFO, "Closing listenSocket");
- listenSocket.close();
- }
- } catch (IOException e) {
- }
-
- if (wifiLock != null) {
- wifiLock.release();
- wifiLock = null;
- }
- clearNotification();
- myLog.d("FTPServerService.onDestroy() finished");
- }
-
- private boolean loadSettings() {
- myLog.l(Log.DEBUG, "Loading settings");
- settings = PreferenceManager.getDefaultSharedPreferences(this);
- //port = Integer.valueOf(settings.getString("portNum", "2121"));
- String portS = settings.getString(getString(R.string.key_port_num),"");
- if (!portS.equals("")) {
- port = Integer.valueOf(portS);
- } else {
- port = Defaults.portNumber;
- }
-
- myLog.l(Log.DEBUG, "Using port " + port);
-
- acceptNet = settings.getBoolean("allowNet", Defaults.acceptNet);
- acceptWifi = settings.getBoolean("allowWifi", Defaults.acceptWifi);
- fullWake = settings.getBoolean(getString(R.string.key_stay_awake), Defaults.stayAwake);
-
- // The username, password, and chrootDir are just checked for sanity
- /*String username = settings.getString("username", null);
- String password = settings.getString("password", null);
- String chrootDir = settings.getString("chrootDir", Defaults.chrootDir);
- */
-
- String username = settings.getString(getString(R.string.key_username),"");
- if (username.equals("")) {
- username = Util.getCode(this);
- }
- String password = settings.getString(getString(R.string.key_ftp_pwd),"");
- if (password.equals("")) {
- password = Util.getCode(this);
- }
- String chrootDir = settings.getString(getString(R.string.key_root_dir),"");
- if (chrootDir.equals("")) {
- chrootDir = "/";
- }
- Log.d("FTPService", "(username):"+username+"(pwd)"+password+"(chroot)"+chrootDir);
-
- validateBlock: {
- if (username == null || password == null) {
- myLog.l(Log.ERROR, "Username or password is invalid");
- break validateBlock;
- }
- File chrootDirAsFile = new File(chrootDir);
- if (!chrootDirAsFile.isDirectory()) {
- myLog.l(Log.ERROR, "Chroot dir is invalid");
- break validateBlock;
- }
-
-
- Globals.setChrootDir(chrootDirAsFile);
- Globals.setUsername(username);
- return true;
- }
- // We reach here if the settings were not sane
- return false;
- }
-
- // This opens a listening socket on all interfaces.
- void setupListener() throws IOException {
- listenSocket = new ServerSocket();
- listenSocket.setReuseAddress(true);
- listenSocket.bind(new InetSocketAddress(port));
- }
-
- private void setupNotification() {
- // http://developer.android.com/guide/topics/ui/notifiers/notifications.html
-
- // Get NotificationManager reference
- String ns = Context.NOTIFICATION_SERVICE;
- notificationMgr = (NotificationManager) getSystemService(ns);
-
- // Instantiate a Notification
- int smallIconId = R.drawable.ftp_notification;
- //Bitmap largeIconId = R.drawable.ftp_notification;
- CharSequence tickerText = getString(R.string.notif_server_starting);
- long when = System.currentTimeMillis();
-
-
- CharSequence contentTitle = getString(R.string.notif_title);
- CharSequence contentText = getString(R.string.notif_text);
- Intent notificationIntent = new Intent(this, getSettingClass());
- PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
- notificationIntent, PendingIntent.FLAG_IMMUTABLE);
-
- Notification notification;
- if (android.os.Build.VERSION.SDK_INT>= android.os.Build.VERSION_CODES.JELLY_BEAN) {
- notification = new Notification.Builder(this)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setSmallIcon(smallIconId)
- //.setLargeIcon(largeIconId)
- .setAutoCancel(false)
- .setContentIntent(contentIntent)
- .build();
-
- } else if (android.os.Build.VERSION.SDK_INT>= Build.VERSION_CODES.HONEYCOMB){
- notification = new Notification.Builder(this)
- .setContentTitle(contentTitle)
- .setContentText(contentText)
- .setSmallIcon(smallIconId)
- .setSmallIcon(smallIconId)
- //.setLargeIcon(largeIconId)
- .setAutoCancel(false)
- .setContentIntent(contentIntent)
- .getNotification();
-
- } else {
- notification = new Notification(smallIconId, tickerText, when);
- notification.contentIntent = contentIntent;
- notification.tickerText = contentTitle;
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
- }
-
- notificationMgr.notify(0, notification);
-
-
- myLog.d("Notication setup done");
- }
-
- private void clearNotification() {
- if (notificationMgr == null) {
- // Get NotificationManager reference
- String ns = Context.NOTIFICATION_SERVICE;
- notificationMgr = (NotificationManager) getSystemService(ns);
- }
- notificationMgr.cancelAll();
- myLog.d("Cleared notification");
- }
-
- public void run() {
- // The UI will want to check the server status to update its
- // start/stop server button
- int consecutiveProxyStartFailures = 0;
- long proxyStartMillis = 0;
-
- myLog.l(Log.DEBUG, "Server thread running");
-
- // set our members according to user preferences
- if (!loadSettings()) {
- // loadSettings returns false if settings are not sane
- cleanupAndStopService();
- sendBroadcast(new Intent(ACTION_FAILEDTOSTART));
- return;
- }
-
- if (!isWifiAndApEnabled()) {
- cleanupAndStopService();
- sendBroadcast(new Intent(ACTION_FAILEDTOSTART));
- return;
- }
-
- // Initialization of wifi
- if (acceptWifi) {
- // If configured to accept connections via wifi, then set up the
- // socket
- try {
- setupListener();
- } catch (IOException e) {
- myLog.l(Log.WARN, "Error opening port, check your network connection.");
- // serverAddress = null;
- cleanupAndStopService();
- return;
- }
- takeWifiLock();
- }
- takeWakeLock();
-
- myLog.l(Log.INFO, "SwiFTP server ready");
- setupNotification();
-
- // A socket is open now, so the FTP server is started, notify rest of world
- sendBroadcast(new Intent(ACTION_STARTED));
-
- while (!shouldExit) {
- if (acceptWifi) {
- if (wifiListener != null) {
- if (!wifiListener.isAlive()) {
- myLog.l(Log.DEBUG, "Joining crashed wifiListener thread");
- try {
- wifiListener.join();
- } catch (InterruptedException e) {
- }
- wifiListener = null;
- }
- }
- if (wifiListener == null) {
- // Either our wifi listener hasn't been created yet, or has
- // crashed,
- // so spawn it
- wifiListener = new TcpListener(listenSocket, this);
- wifiListener.start();
- }
- }
- if (acceptNet) {
- if (proxyConnector != null) {
- if (!proxyConnector.isAlive()) {
- myLog.l(Log.DEBUG, "Joining crashed proxy connector");
- try {
- proxyConnector.join();
- } catch (InterruptedException e) {
- }
- proxyConnector = null;
- long nowMillis = new Date().getTime();
- // myLog.l(Log.DEBUG,
- // "Now:"+nowMillis+" start:"+proxyStartMillis);
- if (nowMillis - proxyStartMillis < 3000) { - // We assume that if the proxy thread crashed within - // 3 - // seconds of starting, it was a startup or - // connection - // failure. - myLog.l(Log.DEBUG, "Incrementing proxy start failures"); - consecutiveProxyStartFailures++; - } else { - // Otherwise assume the proxy started successfully - // and - // crashed later. - myLog.l(Log.DEBUG, "Resetting proxy start failures"); - consecutiveProxyStartFailures = 0; - } - } - } - if (proxyConnector == null) { - long nowMillis = new Date().getTime(); - boolean shouldStartListener = false; - // We want to restart the proxy listener without much delay - // for the first few attempts, but add a much longer delay - // if we consistently fail to connect. - if (consecutiveProxyStartFailures < 3 - && (nowMillis - proxyStartMillis)> 5000) {
- // Retry every 5 seconds for the first 3 tries
- shouldStartListener = true;
- } else if (nowMillis - proxyStartMillis> 30000) {
- // After the first 3 tries, only retry once per 30 sec
- shouldStartListener = true;
- }
- if (shouldStartListener) {
- myLog.l(Log.DEBUG, "Spawning ProxyConnector");
- proxyConnector = new ProxyConnector(this);
- proxyConnector.start();
- proxyStartMillis = nowMillis;
- }
- }
- }
- try {
- // todo: think about using ServerSocket, and just closing
- // the main socket to send an exit signal
- Thread.sleep(WAKE_INTERVAL_MS);
- } catch (InterruptedException e) {
- myLog.l(Log.DEBUG, "Thread interrupted");
- }
- }
-
- terminateAllSessions();
-
- if (proxyConnector != null) {
- proxyConnector.quit();
- proxyConnector = null;
- }
- if (wifiListener != null) {
- wifiListener.quit();
- wifiListener = null;
- }
- shouldExit = false; // we handled the exit flag, so reset it to
- // acknowledge
- myLog.l(Log.DEBUG, "Exiting cleanly, returning from run()");
-
- cleanupAndStopService();
- }
-
- private void terminateAllSessions() {
- myLog.i("Terminating " + sessionThreads.size() + " session thread(s)");
- synchronized (this) {
- for (SessionThread sessionThread : sessionThreads) {
- if (sessionThread != null) {
- sessionThread.closeDataSocket();
- sessionThread.closeSocket();
- }
- }
- }
- }
-
- public void cleanupAndStopService() {
- // Call the Android Service shutdown function
- stopSelf();
- releaseWifiLock();
- releaseWakeLock();
- clearNotification();
- sendBroadcast(new Intent(ACTION_STOPPED));
- }
-
- private void takeWakeLock() {
- if (wakeLock == null) {
- PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
-
- // Many (all?) devices seem to not properly honor a
- // PARTIAL_WAKE_LOCK,
- // which should prevent CPU throttling. This has been
- // well-complained-about on android-developers.
- // For these devices, we have a config option to force the phone
- // into a
- // full wake lock.
- if (fullWake) {
- wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, WAKE_LOCK_TAG);
- } else {
- wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
- }
- wakeLock.setReferenceCounted(false);
- }
- myLog.d("Acquiring wake lock");
- wakeLock.acquire();
- }
-
- private void releaseWakeLock() {
- myLog.d("Releasing wake lock");
- if (wakeLock != null) {
- wakeLock.release();
- wakeLock = null;
- myLog.d("Finished releasing wake lock");
- } else {
- myLog.i("Couldn't release null wake lock");
- }
- }
-
- // public static void writeMonitor(boolean incoming, String s) {
- // if(incoming) {
- // s = "> " + s;
- // } else {
- // s = "< " + s; - // } - // sessionMonitor.add(s.trim()); - // int maxSize = Defaults.getSessionMonitorScrollBack(); - // while(sessionMonitor.size()> maxSize) {
- // sessionMonitor.remove(0);
- // }
- // updateClients();
- // }
-
- private void takeWifiLock() {
- myLog.d("Taking wifi lock");
- if (wifiLock == null) {
- WifiManager manager = (WifiManager) this.getApplication().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
- wifiLock = manager.createWifiLock("SwiFTP");
- wifiLock.setReferenceCounted(false);
- }
- wifiLock.acquire();
- }
-
- private void releaseWifiLock() {
- myLog.d("Releasing wifi lock");
- if (wifiLock != null) {
- wifiLock.release();
- wifiLock = null;
- }
- }
-
- public void errorShutdown() {
- myLog.l(Log.ERROR, "Service errorShutdown() called");
- cleanupAndStopService();
- }
-
- /**
- * The FTPServerService must know about all running session threads so they can be
- * terminated on exit. Called when a new session is created.
- */
- public void registerSessionThread(SessionThread newSession) {
- // Before adding the new session thread, clean up any finished session
- // threads that are present in the list.
-
- // Since we're not allowed to modify the list while iterating over
- // it, we construct a list in toBeRemoved of threads to remove
- // later from the sessionThreads list.
- synchronized (this) {
- List toBeRemoved = new ArrayList();
- for (SessionThread sessionThread : sessionThreads) {
- if (!sessionThread.isAlive()) {
- myLog.l(Log.DEBUG, "Cleaning up finished session...");
- try {
- sessionThread.join();
- myLog.l(Log.DEBUG, "Thread joined");
- toBeRemoved.add(sessionThread);
- sessionThread.closeSocket(); // make sure socket closed
- } catch (InterruptedException e) {
- myLog.l(Log.DEBUG, "Interrupted while joining");
- // We will try again in the next loop iteration
- }
- }
- }
- for (SessionThread removeThread : toBeRemoved) {
- sessionThreads.remove(removeThread);
- }
-
- // Cleanup is complete. Now actually add the new thread to the list.
- sessionThreads.add(newSession);
- }
- myLog.d("Registered session thread");
- }
-
- /** Get the ProxyConnector, may return null if proxying is disabled. */
- public ProxyConnector getProxyConnector() {
- return proxyConnector;
- }
-
- abstract protected Class> getSettingClass();
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+ */
+
+package org.swiftp;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.ServerSocket;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.swiftp.server.ProxyConnector;
+import org.swiftp.server.SessionThread;
+import org.swiftp.server.TcpListener;
+
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiManager.WifiLock;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+public abstract class FTPServerService extends Service implements Runnable {
+
+ // Service will broadcast (LocalBroadcast) when server start/stop
+ static public final String ACTION_STARTED = "org.swiftp.FTPServerService.STARTED";
+ static public final String ACTION_STOPPED = "org.swiftp.FTPServerService.STOPPED";
+ static public final String ACTION_FAILEDTOSTART = "org.swiftp.FTPServerService.FAILEDTOSTART";
+ public static final int BACKLOG = 21;
+ public static final int MAX_SESSIONS = 5;
+ public static final String WAKE_LOCK_TAG = "SwiFTP";
+ // The server thread will check this often to look for incoming
+ // connections. We are forced to use non-blocking accept() and polling
+ // because we cannot wait forever in accept() if we want to be able
+ // to receive an exit signal and cleanly exit.
+ public static final int WAKE_INTERVAL_MS = 1000; // milliseconds
+ private static final int WIFI_AP_STATE_ENABLED = 13;
+ protected static Thread serverThread = null;
+ protected static MyLog staticLog = new MyLog(FTPServerService.class.getName());
+ protected static WifiLock wifiLock = null;
+ protected static List sessionMonitor = new ArrayList();
+ protected static List serverLog = new ArrayList();
+
+ // protected static InetAddress serverAddress = null;
+ protected static int uiLogLevel = Defaults.getUiLogLevel();
+ protected static int port;
+ protected static boolean acceptWifi;
+ protected static boolean acceptNet;
+ protected static boolean fullWake;
+ private static SharedPreferences settings = null;
+ private final List sessionThreads = new ArrayList();
+ protected boolean shouldExit = false;
+ protected MyLog myLog = new MyLog(getClass().getName());
+ // protected ServerSocketChannel wifiSocket;
+ protected ServerSocket listenSocket;
+ NotificationManager notificationMgr = null;
+ PowerManager.WakeLock wakeLock;
+ private TcpListener wifiListener = null;
+ private ProxyConnector proxyConnector = null;
+
+ public FTPServerService() {
+ }
+
+ public static boolean isRunning() {
+ // return true if and only if a server Thread is running
+ if (serverThread == null) {
+ staticLog.l(Log.DEBUG, "Server is not running (null serverThread)");
+ return false;
+ }
+ if (!serverThread.isAlive()) {
+ staticLog.l(Log.DEBUG, "serverThread non-null but !isAlive()");
+ } else {
+ staticLog.l(Log.DEBUG, "Server is alive");
+ }
+ return true;
+ }
+
+ /**
+ * Gets the IP address of the wifi connection.
+ *
+ * @return The integer IP address if wifi enabled, or null if not.
+ */
+ public static InetAddress getWifiIp() {
+ Context myContext = Globals.getContext().getApplicationContext();
+ if (myContext == null) {
+ throw new NullPointerException("Global context is null");
+ }
+ WifiManager wifiMgr = (WifiManager) myContext.getSystemService(Context.WIFI_SERVICE);
+ if (isWifiEnabled()) {
+ int ipAsInt = wifiMgr.getConnectionInfo().getIpAddress();
+ if (ipAsInt == 0) {
+ return null;
+ } else {
+ return Util.intToInet(ipAsInt);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ public static ArrayList getWifiAndApIp(){
+ ArrayList ip = new ArrayList();
+ InetAddress addrWifi = getWifiIp();
+ String hostAddr;
+ if(addrWifi!=null)
+ ip.add(addrWifi.getHostAddress());
+ try {
+ for (NetworkInterface intf : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+ for (InetAddress addr : Collections.list(intf.getInetAddresses())) {
+ hostAddr = addr.getHostAddress();
+ if(hostAddr == null || addr.isLoopbackAddress() || ip.contains(hostAddr))
+ continue;
+ if (hostAddr.contains(".")){
+ ip.add(hostAddr);
+ break;
+ }
+ }
+ }
+ } catch (SocketException ignored) {
+ }
+ if(ip.size()==0)
+ ip = null;
+ return ip;
+ }
+
+ public static String[] getIpPortString(){
+ ArrayList address = getWifiAndApIp();
+ if(address == null)
+ return null;
+ String[] ipPort = new String[address.size()];
+ for(int i = 0; i getSessionMonitorContents() {
+ return new ArrayList(sessionMonitor);
+ }
+
+ public static List getServerLogContents() {
+ return new ArrayList(serverLog);
+ }
+
+ public static void log(int msgLevel, String s) {
+ serverLog.add(s);
+ int maxSize = Defaults.getServerLogScrollBack();
+ while (serverLog.size()> maxSize) {
+ serverLog.remove(0);
+ }
+ // updateClients();
+ }
+
+ public static void writeMonitor(boolean incoming, String s) {
+ }
+
+ public static int getPort() {
+ return port;
+ }
+
+ public static void setPort(int port) {
+ FTPServerService.port = port;
+ }
+
+ static public SharedPreferences getSettings() {
+ return settings;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // We don't implement this functionality, so ignore it
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ myLog.l(Log.DEBUG, "SwiFTP server created");
+ // Set the application-wide context global, if not already set
+ Context myContext = Globals.getContext();
+ if (myContext == null) {
+ myContext = getApplicationContext();
+ if (myContext != null) {
+ Globals.setContext(myContext);
+ }
+ }
+ }
+
+ @Override
+ public void onStart(Intent intent, int startId) {
+ super.onStart(intent, startId);
+
+ shouldExit = false;
+ int attempts = 10;
+ // The previous server thread may still be cleaning up, wait for it
+ // to finish.
+ while (serverThread != null) {
+ myLog.l(Log.WARN, "Won't start, server thread exists");
+ if (attempts> 0) {
+ attempts--;
+ Util.sleepIgnoreInterupt(1000);
+ } else {
+ myLog.l(Log.ERROR, "Server thread already exists");
+ return;
+ }
+ }
+ myLog.l(Log.DEBUG, "Creating server thread");
+ serverThread = new Thread(this);
+ serverThread.start();
+ }
+
+ @Override
+ public void onDestroy() {
+ myLog.l(Log.INFO, "onDestroy() Stopping server");
+ shouldExit = true;
+ if (serverThread == null) {
+ myLog.l(Log.WARN, "Stopping with null serverThread");
+ return;
+ } else {
+ serverThread.interrupt();
+ try {
+ serverThread.join(10000); // wait 10 sec for server thread to
+ // finish
+ } catch (InterruptedException e) {
+ }
+ if (serverThread.isAlive()) {
+ myLog.l(Log.WARN, "Server thread failed to exit");
+ // it may still exit eventually if we just leave the
+ // shouldExit flag set
+ } else {
+ myLog.d("serverThread join()ed ok");
+ serverThread = null;
+ }
+ }
+ try {
+ if (listenSocket != null) {
+ myLog.l(Log.INFO, "Closing listenSocket");
+ listenSocket.close();
+ }
+ } catch (IOException e) {
+ }
+
+ if (wifiLock != null) {
+ wifiLock.release();
+ wifiLock = null;
+ }
+ clearNotification();
+ myLog.d("FTPServerService.onDestroy() finished");
+ }
+
+ private boolean loadSettings() {
+ myLog.l(Log.DEBUG, "Loading settings");
+ loadPort(this);
+
+ myLog.l(Log.DEBUG, "Using port " + port);
+
+ acceptNet = settings.getBoolean("allowNet", Defaults.acceptNet);
+ acceptWifi = settings.getBoolean("allowWifi", Defaults.acceptWifi);
+ fullWake = settings.getBoolean(getString(R.string.key_stay_awake), Defaults.stayAwake);
+
+ // The username, password, and chrootDir are just checked for sanity
+ /*String username = settings.getString("username", null);
+ String password = settings.getString("password", null);
+ String chrootDir = settings.getString("chrootDir", Defaults.chrootDir);
+ */
+
+ String username = settings.getString(getString(R.string.key_username),"");
+ if (username.equals("")) {
+ username = Util.getCode(this);
+ }
+ String password = settings.getString(getString(R.string.key_ftp_pwd),"");
+ if (password.equals("")) {
+ password = Util.getCode(this);
+ }
+ String chrootDir = settings.getString(getString(R.string.key_root_dir),"");
+ if (chrootDir.equals("")) {
+ chrootDir = "/";
+ }
+ Log.d("FTPService", "(username):"+username+"(pwd)"+password+"(chroot)"+chrootDir);
+
+ validateBlock: {
+ if (username == null || password == null) {
+ myLog.l(Log.ERROR, "Username or password is invalid");
+ break validateBlock;
+ }
+ File chrootDirAsFile = new File(chrootDir);
+ if (!chrootDirAsFile.isDirectory()) {
+ myLog.l(Log.ERROR, "Chroot dir is invalid");
+ break validateBlock;
+ }
+
+
+ Globals.setChrootDir(chrootDirAsFile);
+ Globals.setUsername(username);
+ return true;
+ }
+ // We reach here if the settings were not sane
+ return false;
+ }
+
+ public static void loadPort(Context context){
+ settings = PreferenceManager.getDefaultSharedPreferences(context);
+ //port = Integer.valueOf(settings.getString("portNum", "2121"));
+ String portS = settings.getString(context.getString(R.string.key_port_num),"");
+ if (!portS.equals("")) {
+ port = Integer.valueOf(portS);
+ } else {
+ port = Defaults.portNumber;
+ }
+ }
+
+ // This opens a listening socket on all interfaces.
+ void setupListener() throws IOException {
+ listenSocket = new ServerSocket();
+ listenSocket.setReuseAddress(true);
+ listenSocket.bind(new InetSocketAddress(port));
+ }
+
+ private void setupNotification() {
+ // http://developer.android.com/guide/topics/ui/notifiers/notifications.html
+
+ // Get NotificationManager reference
+ String ns = Context.NOTIFICATION_SERVICE;
+ notificationMgr = (NotificationManager) getSystemService(ns);
+
+ // Instantiate a Notification
+ int smallIconId = R.drawable.ftp_notification;
+ //Bitmap largeIconId = R.drawable.ftp_notification;
+ CharSequence tickerText = getString(R.string.notif_server_starting);
+ long when = System.currentTimeMillis();
+
+
+ CharSequence contentTitle = getString(R.string.notif_title);
+ CharSequence contentText = getString(R.string.notif_text);
+ Intent notificationIntent = new Intent(this, getSettingClass());
+ PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+ notificationIntent, PendingIntent.FLAG_IMMUTABLE);
+
+ Notification notification;
+ if (android.os.Build.VERSION.SDK_INT>= android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ notification = new Notification.Builder(this)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setSmallIcon(smallIconId)
+ //.setLargeIcon(largeIconId)
+ .setAutoCancel(false)
+ .setContentIntent(contentIntent)
+ .build();
+
+ } else if (android.os.Build.VERSION.SDK_INT>= Build.VERSION_CODES.HONEYCOMB){
+ notification = new Notification.Builder(this)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setSmallIcon(smallIconId)
+ .setSmallIcon(smallIconId)
+ //.setLargeIcon(largeIconId)
+ .setAutoCancel(false)
+ .setContentIntent(contentIntent)
+ .getNotification();
+
+ } else {
+ notification = new Notification(smallIconId, tickerText, when);
+ notification.contentIntent = contentIntent;
+ notification.tickerText = contentTitle;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ }
+
+ notificationMgr.notify(0, notification);
+
+
+ myLog.d("Notication setup done");
+ }
+
+ private void clearNotification() {
+ if (notificationMgr == null) {
+ // Get NotificationManager reference
+ String ns = Context.NOTIFICATION_SERVICE;
+ notificationMgr = (NotificationManager) getSystemService(ns);
+ }
+ notificationMgr.cancelAll();
+ myLog.d("Cleared notification");
+ }
+
+ public void run() {
+ // The UI will want to check the server status to update its
+ // start/stop server button
+ int consecutiveProxyStartFailures = 0;
+ long proxyStartMillis = 0;
+
+ myLog.l(Log.DEBUG, "Server thread running");
+
+ // set our members according to user preferences
+ if (!loadSettings()) {
+ // loadSettings returns false if settings are not sane
+ cleanupAndStopService();
+ sendBroadcast(new Intent(ACTION_FAILEDTOSTART));
+ return;
+ }
+
+ if (!isWifiAndApEnabled()) {
+ cleanupAndStopService();
+ sendBroadcast(new Intent(ACTION_FAILEDTOSTART));
+ return;
+ }
+
+ // Initialization of wifi
+ if (acceptWifi) {
+ // If configured to accept connections via wifi, then set up the
+ // socket
+ try {
+ setupListener();
+ } catch (IOException e) {
+ myLog.l(Log.WARN, "Error opening port, check your network connection.");
+ // serverAddress = null;
+ cleanupAndStopService();
+ return;
+ }
+ takeWifiLock();
+ }
+ takeWakeLock();
+
+ myLog.l(Log.INFO, "SwiFTP server ready");
+ setupNotification();
+
+ // A socket is open now, so the FTP server is started, notify rest of world
+ sendBroadcast(new Intent(ACTION_STARTED));
+
+ while (!shouldExit) {
+ if (acceptWifi) {
+ if (wifiListener != null) {
+ if (!wifiListener.isAlive()) {
+ myLog.l(Log.DEBUG, "Joining crashed wifiListener thread");
+ try {
+ wifiListener.join();
+ } catch (InterruptedException e) {
+ }
+ wifiListener = null;
+ }
+ }
+ if (wifiListener == null) {
+ // Either our wifi listener hasn't been created yet, or has
+ // crashed,
+ // so spawn it
+ wifiListener = new TcpListener(listenSocket, this);
+ wifiListener.start();
+ }
+ }
+ if (acceptNet) {
+ if (proxyConnector != null) {
+ if (!proxyConnector.isAlive()) {
+ myLog.l(Log.DEBUG, "Joining crashed proxy connector");
+ try {
+ proxyConnector.join();
+ } catch (InterruptedException e) {
+ }
+ proxyConnector = null;
+ long nowMillis = new Date().getTime();
+ // myLog.l(Log.DEBUG,
+ // "Now:"+nowMillis+" start:"+proxyStartMillis);
+ if (nowMillis - proxyStartMillis < 3000) { + // We assume that if the proxy thread crashed within + // 3 + // seconds of starting, it was a startup or + // connection + // failure. + myLog.l(Log.DEBUG, "Incrementing proxy start failures"); + consecutiveProxyStartFailures++; + } else { + // Otherwise assume the proxy started successfully + // and + // crashed later. + myLog.l(Log.DEBUG, "Resetting proxy start failures"); + consecutiveProxyStartFailures = 0; + } + } + } + if (proxyConnector == null) { + long nowMillis = new Date().getTime(); + boolean shouldStartListener = false; + // We want to restart the proxy listener without much delay + // for the first few attempts, but add a much longer delay + // if we consistently fail to connect. + if (consecutiveProxyStartFailures < 3 + && (nowMillis - proxyStartMillis)> 5000) {
+ // Retry every 5 seconds for the first 3 tries
+ shouldStartListener = true;
+ } else if (nowMillis - proxyStartMillis> 30000) {
+ // After the first 3 tries, only retry once per 30 sec
+ shouldStartListener = true;
+ }
+ if (shouldStartListener) {
+ myLog.l(Log.DEBUG, "Spawning ProxyConnector");
+ proxyConnector = new ProxyConnector(this);
+ proxyConnector.start();
+ proxyStartMillis = nowMillis;
+ }
+ }
+ }
+ try {
+ // todo: think about using ServerSocket, and just closing
+ // the main socket to send an exit signal
+ Thread.sleep(WAKE_INTERVAL_MS);
+ } catch (InterruptedException e) {
+ myLog.l(Log.DEBUG, "Thread interrupted");
+ }
+ }
+
+ terminateAllSessions();
+
+ if (proxyConnector != null) {
+ proxyConnector.quit();
+ proxyConnector = null;
+ }
+ if (wifiListener != null) {
+ wifiListener.quit();
+ wifiListener = null;
+ }
+ shouldExit = false; // we handled the exit flag, so reset it to
+ // acknowledge
+ myLog.l(Log.DEBUG, "Exiting cleanly, returning from run()");
+
+ cleanupAndStopService();
+ }
+
+ private void terminateAllSessions() {
+ myLog.i("Terminating " + sessionThreads.size() + " session thread(s)");
+ synchronized (this) {
+ for (SessionThread sessionThread : sessionThreads) {
+ if (sessionThread != null) {
+ sessionThread.closeDataSocket();
+ sessionThread.closeSocket();
+ }
+ }
+ }
+ }
+
+ public void cleanupAndStopService() {
+ // Call the Android Service shutdown function
+ stopSelf();
+ releaseWifiLock();
+ releaseWakeLock();
+ clearNotification();
+ sendBroadcast(new Intent(ACTION_STOPPED));
+ }
+
+ private void takeWakeLock() {
+ if (wakeLock == null) {
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+
+ // Many (all?) devices seem to not properly honor a
+ // PARTIAL_WAKE_LOCK,
+ // which should prevent CPU throttling. This has been
+ // well-complained-about on android-developers.
+ // For these devices, we have a config option to force the phone
+ // into a
+ // full wake lock.
+ if (fullWake) {
+ wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, WAKE_LOCK_TAG);
+ } else {
+ wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG);
+ }
+ wakeLock.setReferenceCounted(false);
+ }
+ myLog.d("Acquiring wake lock");
+ wakeLock.acquire();
+ }
+
+ private void releaseWakeLock() {
+ myLog.d("Releasing wake lock");
+ if (wakeLock != null) {
+ wakeLock.release();
+ wakeLock = null;
+ myLog.d("Finished releasing wake lock");
+ } else {
+ myLog.i("Couldn't release null wake lock");
+ }
+ }
+
+ // public static void writeMonitor(boolean incoming, String s) {
+ // if(incoming) {
+ // s = "> " + s;
+ // } else {
+ // s = "< " + s; + // } + // sessionMonitor.add(s.trim()); + // int maxSize = Defaults.getSessionMonitorScrollBack(); + // while(sessionMonitor.size()> maxSize) {
+ // sessionMonitor.remove(0);
+ // }
+ // updateClients();
+ // }
+
+ private void takeWifiLock() {
+ myLog.d("Taking wifi lock");
+ if (wifiLock == null) {
+ WifiManager manager = (WifiManager) this.getApplication().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ wifiLock = manager.createWifiLock("SwiFTP");
+ wifiLock.setReferenceCounted(false);
+ }
+ wifiLock.acquire();
+ }
+
+ private void releaseWifiLock() {
+ myLog.d("Releasing wifi lock");
+ if (wifiLock != null) {
+ wifiLock.release();
+ wifiLock = null;
+ }
+ }
+
+ public void errorShutdown() {
+ myLog.l(Log.ERROR, "Service errorShutdown() called");
+ cleanupAndStopService();
+ }
+
+ /**
+ * The FTPServerService must know about all running session threads so they can be
+ * terminated on exit. Called when a new session is created.
+ */
+ public void registerSessionThread(SessionThread newSession) {
+ // Before adding the new session thread, clean up any finished session
+ // threads that are present in the list.
+
+ // Since we're not allowed to modify the list while iterating over
+ // it, we construct a list in toBeRemoved of threads to remove
+ // later from the sessionThreads list.
+ synchronized (this) {
+ List toBeRemoved = new ArrayList();
+ for (SessionThread sessionThread : sessionThreads) {
+ if (!sessionThread.isAlive()) {
+ myLog.l(Log.DEBUG, "Cleaning up finished session...");
+ try {
+ sessionThread.join();
+ myLog.l(Log.DEBUG, "Thread joined");
+ toBeRemoved.add(sessionThread);
+ sessionThread.closeSocket(); // make sure socket closed
+ } catch (InterruptedException e) {
+ myLog.l(Log.DEBUG, "Interrupted while joining");
+ // We will try again in the next loop iteration
+ }
+ }
+ }
+ for (SessionThread removeThread : toBeRemoved) {
+ sessionThreads.remove(removeThread);
+ }
+
+ // Cleanup is complete. Now actually add the new thread to the list.
+ sessionThreads.add(newSession);
+ }
+ myLog.d("Registered session thread");
+ }
+
+ /** Get the ProxyConnector, may return null if proxying is disabled. */
+ public ProxyConnector getProxyConnector() {
+ return proxyConnector;
+ }
+
+ abstract protected Class> getSettingClass();
+}
diff --git a/src/main/java/org/swiftp/Globals.java b/src/main/java/org/swiftp/Globals.java
index 254661686bfa178b99f02b2d8455ff9f53f2ccbf..8def1ef3c79a7ae38366abb2ac1227f5d04c7a6f 100644
--- a/src/main/java/org/swiftp/Globals.java
+++ b/src/main/java/org/swiftp/Globals.java
@@ -1,68 +1,68 @@
-package org.swiftp;
-
-import java.io.File;
-
-import org.swiftp.server.ProxyConnector;
-
-import android.content.Context;
-
-// TODO: this must all be removed
-// if you need a setting, get it from the settings
-
-public class Globals {
- private static Context context;
- private static String lastError;
- private static File chrootDir = null;
- private static ProxyConnector proxyConnector = null;
- private static String username = null;
-
- public static ProxyConnector getProxyConnector() {
- if(proxyConnector != null) {
- if(!proxyConnector.isAlive()) {
- return null;
- }
- }
- return proxyConnector;
- }
-
- public static void setProxyConnector(ProxyConnector proxyConnector) {
- Globals.proxyConnector = proxyConnector;
- }
-
- public static File getChrootDir() {
- return chrootDir;
- }
-
- public static void setChrootDir(File chrootDir) {
- if(chrootDir.isDirectory()) {
- Globals.chrootDir = chrootDir;
- }
- }
-
- public static String getLastError() {
- return lastError;
- }
-
- public static void setLastError(String lastError) {
- Globals.lastError = lastError;
- }
-
- public static Context getContext() {
- return context;
- }
-
- public static void setContext(Context context) {
- if(context != null) {
- Globals.context = context;
- }
- }
-
- public static String getUsername() {
- return username;
- }
-
- public static void setUsername(String username) {
- Globals.username = username;
- }
-
-}
+package org.swiftp;
+
+import java.io.File;
+
+import org.swiftp.server.ProxyConnector;
+
+import android.content.Context;
+
+// TODO: this must all be removed
+// if you need a setting, get it from the settings
+
+public class Globals {
+ private static Context context;
+ private static String lastError;
+ private static File chrootDir = null;
+ private static ProxyConnector proxyConnector = null;
+ private static String username = null;
+
+ public static ProxyConnector getProxyConnector() {
+ if(proxyConnector != null) {
+ if(!proxyConnector.isAlive()) {
+ return null;
+ }
+ }
+ return proxyConnector;
+ }
+
+ public static void setProxyConnector(ProxyConnector proxyConnector) {
+ Globals.proxyConnector = proxyConnector;
+ }
+
+ public static File getChrootDir() {
+ return chrootDir;
+ }
+
+ public static void setChrootDir(File chrootDir) {
+ if(chrootDir.isDirectory()) {
+ Globals.chrootDir = chrootDir;
+ }
+ }
+
+ public static String getLastError() {
+ return lastError;
+ }
+
+ public static void setLastError(String lastError) {
+ Globals.lastError = lastError;
+ }
+
+ public static Context getContext() {
+ return context;
+ }
+
+ public static void setContext(Context context) {
+ if(context != null) {
+ Globals.context = context;
+ }
+ }
+
+ public static String getUsername() {
+ return username;
+ }
+
+ public static void setUsername(String username) {
+ Globals.username = username;
+ }
+
+}
diff --git a/src/main/java/org/swiftp/MyLog.java b/src/main/java/org/swiftp/MyLog.java
index a512bee68f1056a7fcaf0206fdcd905e93520344..a2fd284e5aac63481693e41a9ebe98cc0a6cddda 100644
--- a/src/main/java/org/swiftp/MyLog.java
+++ b/src/main/java/org/swiftp/MyLog.java
@@ -1,65 +1,65 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp;
-
-import android.util.Log;
-
-public class MyLog {
- protected String tag;
-
- public MyLog(String tag) {
- this.tag = tag;
- }
-
- public void l(int level, String str, boolean sysOnly) {
- synchronized (MyLog.class) {
- str = str.trim();
- // Messages of this severity are handled specially
- if(level == Log.ERROR || level == Log.WARN) {
- Globals.setLastError(str);
- }
- if(level>= Defaults.getConsoleLogLevel()) {
- Log.println(level,tag, str);
- }
- if(!sysOnly) { // some messages only go to the Android log
- if(level>= Defaults.getUiLogLevel()) {
- FTPServerService.log(level, str);
- }
- }
- }
- }
-
- public void l(int level, String str) {
- l(level, str, false);
- }
-
- public void e(String s) {
- l(Log.ERROR, s, false);
- }
- public void w(String s) {
- l(Log.WARN, s, false);
- }
- public void i(String s) {
- l(Log.INFO, s, false);
- }
- public void d(String s) {
- l(Log.DEBUG, s, false);
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp;
+
+import android.util.Log;
+
+public class MyLog {
+ protected String tag;
+
+ public MyLog(String tag) {
+ this.tag = tag;
+ }
+
+ public void l(int level, String str, boolean sysOnly) {
+ synchronized (MyLog.class) {
+ str = str.trim();
+ // Messages of this severity are handled specially
+ if(level == Log.ERROR || level == Log.WARN) {
+ Globals.setLastError(str);
+ }
+ if(level>= Defaults.getConsoleLogLevel()) {
+ Log.println(level,tag, str);
+ }
+ if(!sysOnly) { // some messages only go to the Android log
+ if(level>= Defaults.getUiLogLevel()) {
+ FTPServerService.log(level, str);
+ }
+ }
+ }
+ }
+
+ public void l(int level, String str) {
+ l(level, str, false);
+ }
+
+ public void e(String s) {
+ l(Log.ERROR, s, false);
+ }
+ public void w(String s) {
+ l(Log.WARN, s, false);
+ }
+ public void i(String s) {
+ l(Log.INFO, s, false);
+ }
+ public void d(String s) {
+ l(Log.DEBUG, s, false);
+ }
+}
diff --git a/src/main/java/org/swiftp/QuotaStats.java b/src/main/java/org/swiftp/QuotaStats.java
index f2c5e75e7507b73d1d9f139f45bd9f2064847df1..d86b738c34d0dd5e47fdf387824c716cbd9e9816 100644
--- a/src/main/java/org/swiftp/QuotaStats.java
+++ b/src/main/java/org/swiftp/QuotaStats.java
@@ -1,37 +1,37 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp;
-
-public class QuotaStats {
- private int quota;
- private int used;
-
- public QuotaStats(int used, int quota) {
- this.quota = quota;
- this.used = used;
- }
-
- public int getQuota() {
- return quota;
- }
- public int getUsed() {
- return used;
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp;
+
+public class QuotaStats {
+ private int quota;
+ private int used;
+
+ public QuotaStats(int used, int quota) {
+ this.quota = quota;
+ this.used = used;
+ }
+
+ public int getQuota() {
+ return quota;
+ }
+ public int getUsed() {
+ return used;
+ }
+}
diff --git a/src/main/java/org/swiftp/Settings.java b/src/main/java/org/swiftp/Settings.java
index 7997fe6996dbf6ce5898086f67825375d45ad6a7..2ec7aa0ea6015fc30e755c82a3d3481ee2b7e62e 100644
--- a/src/main/java/org/swiftp/Settings.java
+++ b/src/main/java/org/swiftp/Settings.java
@@ -1,83 +1,83 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp;
-
-import android.util.Log;
-
-public class Settings {
- protected static int inputBufferSize = 256;
- protected static boolean allowOverwrite = false;
- protected static int dataChunkSize = 8192; // do file I/O in 8k chunks
- protected static int sessionMonitorScrollBack = 10;
- protected static int serverLogScrollBack = 10;
- protected static int uiLogLevel = Log.INFO;
-
- public static int getUiLogLevel() {
- return uiLogLevel;
- }
-
- public static void setUiLogLevel(int uiLogLevel) {
- Settings.uiLogLevel = uiLogLevel;
- }
-
- public static int getInputBufferSize() {
- return inputBufferSize;
- }
-
- public static void setInputBufferSize(int inputBufferSize) {
- Settings.inputBufferSize = inputBufferSize;
- }
-
- public static boolean isAllowOverwrite() {
- return allowOverwrite;
- }
-
- public static void setAllowOverwrite(boolean allowOverwrite) {
- Settings.allowOverwrite = allowOverwrite;
- }
-
- public static int getDataChunkSize() {
- return dataChunkSize;
- }
-
- public static void setDataChunkSize(int dataChunkSize) {
- Settings.dataChunkSize = dataChunkSize;
- }
-
- public static int getSessionMonitorScrollBack() {
- return sessionMonitorScrollBack;
- }
-
- public static void setSessionMonitorScrollBack(
- int sessionMonitorScrollBack)
- {
- Settings.sessionMonitorScrollBack = sessionMonitorScrollBack;
- }
-
- public static int getServerLogScrollBack() {
- return serverLogScrollBack;
- }
-
- public static void setLogScrollBack(int serverLogScrollBack) {
- Settings.serverLogScrollBack = serverLogScrollBack;
- }
-
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp;
+
+import android.util.Log;
+
+public class Settings {
+ protected static int inputBufferSize = 256;
+ protected static boolean allowOverwrite = false;
+ protected static int dataChunkSize = 8192; // do file I/O in 8k chunks
+ protected static int sessionMonitorScrollBack = 10;
+ protected static int serverLogScrollBack = 10;
+ protected static int uiLogLevel = Log.INFO;
+
+ public static int getUiLogLevel() {
+ return uiLogLevel;
+ }
+
+ public static void setUiLogLevel(int uiLogLevel) {
+ Settings.uiLogLevel = uiLogLevel;
+ }
+
+ public static int getInputBufferSize() {
+ return inputBufferSize;
+ }
+
+ public static void setInputBufferSize(int inputBufferSize) {
+ Settings.inputBufferSize = inputBufferSize;
+ }
+
+ public static boolean isAllowOverwrite() {
+ return allowOverwrite;
+ }
+
+ public static void setAllowOverwrite(boolean allowOverwrite) {
+ Settings.allowOverwrite = allowOverwrite;
+ }
+
+ public static int getDataChunkSize() {
+ return dataChunkSize;
+ }
+
+ public static void setDataChunkSize(int dataChunkSize) {
+ Settings.dataChunkSize = dataChunkSize;
+ }
+
+ public static int getSessionMonitorScrollBack() {
+ return sessionMonitorScrollBack;
+ }
+
+ public static void setSessionMonitorScrollBack(
+ int sessionMonitorScrollBack)
+ {
+ Settings.sessionMonitorScrollBack = sessionMonitorScrollBack;
+ }
+
+ public static int getServerLogScrollBack() {
+ return serverLogScrollBack;
+ }
+
+ public static void setLogScrollBack(int serverLogScrollBack) {
+ Settings.serverLogScrollBack = serverLogScrollBack;
+ }
+
+
+}
diff --git a/src/main/java/org/swiftp/Util.java b/src/main/java/org/swiftp/Util.java
index 6d71ec8031c01c7f1ba2f4f302f4589ffe2482e4..70f78d925eea1377bdfbccb292436c61a36dbf94 100644
--- a/src/main/java/org/swiftp/Util.java
+++ b/src/main/java/org/swiftp/Util.java
@@ -1,198 +1,198 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp;
-
-import java.io.UnsupportedEncodingException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.Editor;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.net.Uri;
-import android.provider.Settings;
-import android.util.Log;
-
-abstract public class Util {
- static MyLog myLog = new MyLog(Util.class.getName());
- public static String getAndroidId() {
- ContentResolver cr = Globals.getContext().getContentResolver();
- return Settings.Secure.getString(cr, Settings.Secure.ANDROID_ID);
- }
-
- /**
- * Get the SwiFTP version from the manifest.
- * @return The version as a String.
- */
- public static String getVersion() {
- String packageName = Globals.getContext().getPackageName();
- try {
- return Globals.getContext().getPackageManager().getPackageInfo(packageName, 0).versionName;
- } catch ( NameNotFoundException e) {
- myLog.l(Log.ERROR, "NameNotFoundException looking up SwiFTP version");
- return null;
- }
- }
-
-
- public static byte byteOfInt(int value, int which) {
- int shift = which * 8;
- return (byte)(value>> shift);
- }
-
- public static String ipToString(int addr, String sep) {
- //myLog.l(Log.DEBUG, "IP as int: " + addr);
- if(addr> 0) {
- StringBuffer buf = new StringBuffer();
- buf.
- append(byteOfInt(addr, 0)).append(sep).
- append(byteOfInt(addr, 1)).append(sep).
- append(byteOfInt(addr, 2)).append(sep).
- append(byteOfInt(addr, 3));
- myLog.l(Log.DEBUG, "ipToString returning: " + buf.toString());
- return buf.toString();
- } else {
- return null;
- }
- }
-
- public static InetAddress intToInet(int value) {
- byte[] bytes = new byte[4];
- for(int i = 0; i<4; i++) { - bytes[i] = byteOfInt(value, i); - } - try { - return InetAddress.getByAddress(bytes); - } catch (UnknownHostException e) { - // This only happens if the byte array has a bad length - return null; - } - } - - public static String ipToString(int addr) { - if(addr == 0) { - // This can only occur due to an error, we shouldn't blindly - // convert 0 to string. - myLog.l(Log.INFO, "ipToString won't convert value 0"); - return null; - } - return ipToString(addr, "."); - } - - // This exists to avoid cluttering up other code with - // UnsupportedEncodingExceptions. - public static byte[] jsonToByteArray(JSONObject json) throws JSONException { - try { - return json.toString().getBytes(Defaults.STRING_ENCODING); - } catch (UnsupportedEncodingException e) { - return null; - } - } - - // This exists to avoid cluttering up other code with - // UnsupportedEncodingExceptions. - public static JSONObject byteArrayToJson(byte[] bytes) throws JSONException { - try { - return new JSONObject(new String(bytes, Defaults.STRING_ENCODING)); - } catch (UnsupportedEncodingException e) { - // This will never happen because we use valid encodings - return null; - } - } - - public static void newFileNotify(String path) { - if(Defaults.do_mediascanner_notify) { - myLog.l(Log.DEBUG, "Notifying others about new file: " + path); - new MediaScannerNotifier(Globals.getContext(), path); - } - } - - public static void deletedFileNotify(String path) { - // This might not work, I couldn't find an API call for this. - if(Defaults.do_mediascanner_notify) { - myLog.l(Log.DEBUG, "Notifying others about deleted file: " + path); - new MediaScannerNotifier(Globals.getContext(), path); - } - } - - // A class to help notify the Music Player and other media services when - // a file has been uploaded. Thanks to Dave Sparks in his post to the - // Android Developers mailing list on 14 Feb 2009. - private static class MediaScannerNotifier implements MediaScannerConnectionClient { - private final MediaScannerConnection connection; - private final String path; - - public MediaScannerNotifier(Context context, String path) { - this.path = path; - connection = new MediaScannerConnection(context, this); - connection.connect(); - } - - public void onMediaScannerConnected() { - connection.scanFile(path, null); // null: we don't know MIME type - } - - public void onScanCompleted(String path, Uri uri) { - connection.disconnect(); - } - } - - public static String[] concatStrArrays(String[] a1, String[] a2) { - String[] retArr = new String[a1.length + a2.length]; - System.arraycopy(a1, 0, retArr, 0, a1.length); - System.arraycopy(a2, 0, retArr, a1.length, a2.length); - return retArr; - } - - public static void sleepIgnoreInterupt(long millis) { - try { - Thread.sleep(millis); - } catch(InterruptedException e) {} - } - - public static String getCode(Context context) { - String packageName = context.getPackageName(); - String[] xcode = packageName.split("\\."); - String code = xcode[xcode.length-1]; - return code; - } - - public static String getSP(Context context, String key) { - String val; - SharedPreferences obj = context.getSharedPreferences("passinger_db",0); - val = obj.getString(key,""); - return val; - } - public static void setSP(Context context, String key,String val) { - SharedPreferences obj = context.getSharedPreferences("passinger_db",0); - Editor wobj; - wobj = obj.edit(); - wobj.putString(key, val); - wobj.commit(); - } - -} +/* +Copyright 2009 David Revell + +This file is part of SwiFTP. + +SwiFTP is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +SwiFTP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with SwiFTP. If not, see .
+*/
+
+package org.swiftp;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.net.Uri;
+import android.provider.Settings;
+import android.util.Log;
+
+abstract public class Util {
+ static MyLog myLog = new MyLog(Util.class.getName());
+ public static String getAndroidId() {
+ ContentResolver cr = Globals.getContext().getContentResolver();
+ return Settings.Secure.getString(cr, Settings.Secure.ANDROID_ID);
+ }
+
+ /**
+ * Get the SwiFTP version from the manifest.
+ * @return The version as a String.
+ */
+ public static String getVersion() {
+ String packageName = Globals.getContext().getPackageName();
+ try {
+ return Globals.getContext().getPackageManager().getPackageInfo(packageName, 0).versionName;
+ } catch ( NameNotFoundException e) {
+ myLog.l(Log.ERROR, "NameNotFoundException looking up SwiFTP version");
+ return null;
+ }
+ }
+
+
+ public static byte byteOfInt(int value, int which) {
+ int shift = which * 8;
+ return (byte)(value>> shift);
+ }
+
+ public static String ipToString(int addr, String sep) {
+ //myLog.l(Log.DEBUG, "IP as int: " + addr);
+ if(addr> 0) {
+ StringBuffer buf = new StringBuffer();
+ buf.
+ append(byteOfInt(addr, 0)).append(sep).
+ append(byteOfInt(addr, 1)).append(sep).
+ append(byteOfInt(addr, 2)).append(sep).
+ append(byteOfInt(addr, 3));
+ myLog.l(Log.DEBUG, "ipToString returning: " + buf.toString());
+ return buf.toString();
+ } else {
+ return null;
+ }
+ }
+
+ public static InetAddress intToInet(int value) {
+ byte[] bytes = new byte[4];
+ for(int i = 0; i<4; i++) { + bytes[i] = byteOfInt(value, i); + } + try { + return InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + // This only happens if the byte array has a bad length + return null; + } + } + + public static String ipToString(int addr) { + if(addr == 0) { + // This can only occur due to an error, we shouldn't blindly + // convert 0 to string. + myLog.l(Log.INFO, "ipToString won't convert value 0"); + return null; + } + return ipToString(addr, "."); + } + + // This exists to avoid cluttering up other code with + // UnsupportedEncodingExceptions. + public static byte[] jsonToByteArray(JSONObject json) throws JSONException { + try { + return json.toString().getBytes(Defaults.STRING_ENCODING); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + // This exists to avoid cluttering up other code with + // UnsupportedEncodingExceptions. + public static JSONObject byteArrayToJson(byte[] bytes) throws JSONException { + try { + return new JSONObject(new String(bytes, Defaults.STRING_ENCODING)); + } catch (UnsupportedEncodingException e) { + // This will never happen because we use valid encodings + return null; + } + } + + public static void newFileNotify(String path) { + if(Defaults.do_mediascanner_notify) { + myLog.l(Log.DEBUG, "Notifying others about new file: " + path); + new MediaScannerNotifier(Globals.getContext(), path); + } + } + + public static void deletedFileNotify(String path) { + // This might not work, I couldn't find an API call for this. + if(Defaults.do_mediascanner_notify) { + myLog.l(Log.DEBUG, "Notifying others about deleted file: " + path); + new MediaScannerNotifier(Globals.getContext(), path); + } + } + + // A class to help notify the Music Player and other media services when + // a file has been uploaded. Thanks to Dave Sparks in his post to the + // Android Developers mailing list on 14 Feb 2009. + private static class MediaScannerNotifier implements MediaScannerConnectionClient { + private final MediaScannerConnection connection; + private final String path; + + public MediaScannerNotifier(Context context, String path) { + this.path = path; + connection = new MediaScannerConnection(context, this); + connection.connect(); + } + + public void onMediaScannerConnected() { + connection.scanFile(path, null); // null: we don't know MIME type + } + + public void onScanCompleted(String path, Uri uri) { + connection.disconnect(); + } + } + + public static String[] concatStrArrays(String[] a1, String[] a2) { + String[] retArr = new String[a1.length + a2.length]; + System.arraycopy(a1, 0, retArr, 0, a1.length); + System.arraycopy(a2, 0, retArr, a1.length, a2.length); + return retArr; + } + + public static void sleepIgnoreInterupt(long millis) { + try { + Thread.sleep(millis); + } catch(InterruptedException e) {} + } + + public static String getCode(Context context) { + String packageName = context.getPackageName(); + String[] xcode = packageName.split("\\."); + String code = xcode[xcode.length-1]; + return code; + } + + public static String getSP(Context context, String key) { + String val; + SharedPreferences obj = context.getSharedPreferences("passinger_db",0); + val = obj.getString(key,""); + return val; + } + public static void setSP(Context context, String key,String val) { + SharedPreferences obj = context.getSharedPreferences("passinger_db",0); + Editor wobj; + wobj = obj.edit(); + wobj.putString(key, val); + wobj.commit(); + } + +} diff --git a/src/main/java/org/swiftp/WidgetProvider.java b/src/main/java/org/swiftp/WidgetProvider.java index 85320488dee7e7697f739bad2bdad433fc1468e4..ae661a5382ecf8582bd20fffe7040bac923cbe12 100644 --- a/src/main/java/org/swiftp/WidgetProvider.java +++ b/src/main/java/org/swiftp/WidgetProvider.java @@ -1,121 +1,121 @@ -package org.swiftp; - - -import android.annotation.TargetApi; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.Context; -import android.content.Intent; -import android.view.View; -import android.widget.RemoteViews; - -/** - * Class handles Widget Events. - * - */ -@TargetApi(3) -public class WidgetProvider extends AppWidgetProvider { - - public static String ACTION_WIDGET_BUTTON = "actionWidgetButton"; - - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, - int[] appWidgetIds) { - - // Log.d("MyActivity", "onUpdate"); - // Toast.makeText(context, "onUpdate", Toast.LENGTH_SHORT).show(); - - // register new Widgets, for them to be Updated by WidgetUiUpdater - WidgetUiUpdater.registerWidgets(appWidgetIds, context, appWidgetManager); - - // add ButtonListener - RemoteViews remoteViews = new RemoteViews(context.getPackageName(), - R.layout.ftp_widget); - - Intent active = new Intent(context, WidgetProvider.class); - active.setAction(ACTION_WIDGET_BUTTON); - - PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, - active, PendingIntent.FLAG_IMMUTABLE); - remoteViews.setOnClickPendingIntent(R.id.widget_button_off, actionPendingIntent); - remoteViews.setOnClickPendingIntent(R.id.widget_button_on, actionPendingIntent); - - // set the right state, according to the FTP Server - if (FTPServerService.isRunning()) { - remoteViews.setViewVisibility(R.id.widget_button_on, View.VISIBLE); - remoteViews.setViewVisibility(R.id.widget_button_off, View.GONE); - } else { - remoteViews.setViewVisibility(R.id.widget_button_on, View.GONE); - remoteViews.setViewVisibility(R.id.widget_button_off, View.VISIBLE); - } - appWidgetManager.updateAppWidget(appWidgetIds, remoteViews); - - super.onUpdate(context, appWidgetManager, appWidgetIds); - } - - @Override - public void onReceive(Context context, Intent intent) { - - // Log.d("MyActivity", "onReceive"); - // Toast.makeText(context, "onReceive, Action: "+ intent.getAction(), - // Toast.LENGTH_SHORT).show(); - - if (intent.getAction().equals(ACTION_WIDGET_BUTTON)) { - - /* - * After handling onReceive this Object will be destroyed by the OS. - * BroadcastReceivers and WidgetProviders shouldn't handle asynchronous - * actions, like UI Updates. - */ - - // start or stop FTP Service - Intent intentFTP = new Intent(context, FTPServerService.class); - - if (!FTPServerService.isRunning()) { - context.startService(intentFTP); - } else { - context.stopService(intentFTP); - } - // TODO: Use intent to update UI - // UiUpdater.updateClients(); - } - super.onReceive(context, intent); - } - - @Override - public void onDeleted(Context context, int[] appWidgetIds) { - - // Log.d("MyActivity", "onDeleted"); - // Toast.makeText(context, "onDeleted", Toast.LENGTH_SHORT).show(); - - WidgetUiUpdater.unregisterWidgets(appWidgetIds); - super.onDeleted(context, appWidgetIds); - } - - @Override - public void onEnabled(Context context) { - - // Log.d("MyActivity", "onEnabled"); - // Toast.makeText(context, "onEnabled", Toast.LENGTH_SHORT).show(); - - // register the WidgetUiUpdater for the UiUpdater Messages - WidgetUiUpdater.registerAtUiUpdater(); - super.onEnabled(context); - } - - @Override - public void onDisabled(Context context) { - - // Log.d("MyActivity", "onDisabled"); - // Toast.makeText(context, "onDisabled", Toast.LENGTH_SHORT).show(); - - // unregister the WidgetUiUpdater from the UiUpdater Messages - WidgetUiUpdater.unregisterAtUiUpdater(); - - // and clean the WidgetIds list, if it is still not empty - WidgetUiUpdater.unregisterAllWidgets(); - super.onDisabled(context); - } - -} +package org.swiftp; + + +import android.annotation.TargetApi; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.view.View; +import android.widget.RemoteViews; + +/** + * Class handles Widget Events. + * + */ +@TargetApi(3) +public class WidgetProvider extends AppWidgetProvider { + + public static String ACTION_WIDGET_BUTTON = "actionWidgetButton"; + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, + int[] appWidgetIds) { + + // Log.d("MyActivity", "onUpdate"); + // Toast.makeText(context, "onUpdate", Toast.LENGTH_SHORT).show(); + + // register new Widgets, for them to be Updated by WidgetUiUpdater + WidgetUiUpdater.registerWidgets(appWidgetIds, context, appWidgetManager); + + // add ButtonListener + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), + R.layout.ftp_widget); + + Intent active = new Intent(context, WidgetProvider.class); + active.setAction(ACTION_WIDGET_BUTTON); + + PendingIntent actionPendingIntent = PendingIntent.getBroadcast(context, 0, + active, PendingIntent.FLAG_IMMUTABLE); + remoteViews.setOnClickPendingIntent(R.id.widget_button_off, actionPendingIntent); + remoteViews.setOnClickPendingIntent(R.id.widget_button_on, actionPendingIntent); + + // set the right state, according to the FTP Server + if (FTPServerService.isRunning()) { + remoteViews.setViewVisibility(R.id.widget_button_on, View.VISIBLE); + remoteViews.setViewVisibility(R.id.widget_button_off, View.GONE); + } else { + remoteViews.setViewVisibility(R.id.widget_button_on, View.GONE); + remoteViews.setViewVisibility(R.id.widget_button_off, View.VISIBLE); + } + appWidgetManager.updateAppWidget(appWidgetIds, remoteViews); + + super.onUpdate(context, appWidgetManager, appWidgetIds); + } + + @Override + public void onReceive(Context context, Intent intent) { + + // Log.d("MyActivity", "onReceive"); + // Toast.makeText(context, "onReceive, Action: "+ intent.getAction(), + // Toast.LENGTH_SHORT).show(); + + if (intent.getAction().equals(ACTION_WIDGET_BUTTON)) { + + /* + * After handling onReceive this Object will be destroyed by the OS. + * BroadcastReceivers and WidgetProviders shouldn't handle asynchronous + * actions, like UI Updates. + */ + + // start or stop FTP Service + Intent intentFTP = new Intent(context, FTPServerService.class); + + if (!FTPServerService.isRunning()) { + context.startService(intentFTP); + } else { + context.stopService(intentFTP); + } + // TODO: Use intent to update UI + // UiUpdater.updateClients(); + } + super.onReceive(context, intent); + } + + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + + // Log.d("MyActivity", "onDeleted"); + // Toast.makeText(context, "onDeleted", Toast.LENGTH_SHORT).show(); + + WidgetUiUpdater.unregisterWidgets(appWidgetIds); + super.onDeleted(context, appWidgetIds); + } + + @Override + public void onEnabled(Context context) { + + // Log.d("MyActivity", "onEnabled"); + // Toast.makeText(context, "onEnabled", Toast.LENGTH_SHORT).show(); + + // register the WidgetUiUpdater for the UiUpdater Messages + WidgetUiUpdater.registerAtUiUpdater(); + super.onEnabled(context); + } + + @Override + public void onDisabled(Context context) { + + // Log.d("MyActivity", "onDisabled"); + // Toast.makeText(context, "onDisabled", Toast.LENGTH_SHORT).show(); + + // unregister the WidgetUiUpdater from the UiUpdater Messages + WidgetUiUpdater.unregisterAtUiUpdater(); + + // and clean the WidgetIds list, if it is still not empty + WidgetUiUpdater.unregisterAllWidgets(); + super.onDisabled(context); + } + +} diff --git a/src/main/java/org/swiftp/WidgetUiUpdater.java b/src/main/java/org/swiftp/WidgetUiUpdater.java index 0212d0f84e884957a82f61522651c1ecb49ba400..63f1a4c012d50abff1765a787d8e9bb7ca5beeff 100644 --- a/src/main/java/org/swiftp/WidgetUiUpdater.java +++ b/src/main/java/org/swiftp/WidgetUiUpdater.java @@ -1,124 +1,124 @@ -package org.swiftp; - -import java.util.HashSet; - -import android.annotation.TargetApi; -import android.appwidget.AppWidgetManager; -import android.content.Context; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.widget.RemoteViews; - -/** - * This class maintains Widget Updates. It obtains information about UI Updates from - * UiUpdater.class. For starting getting UiUpdates, method {@link #registerAtUiUpdater()} - * have to be executed once. - * - * Why does this Class exist? This class exists, because I was not able to store - * {@link #handler} for every single Widget. There was a Problem with unregistering - * handlers, when a Widget was deleted. - */ -public class WidgetUiUpdater { - private static HashSet widgetIds = new HashSet();
- private static Context context;
- private static AppWidgetManager appWidgetManager;
-
- @SuppressWarnings("unused")
- private static Handler handler = new Handler() {
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case 0: // We are being told to do a UI update
- // If more than one UI update is queued up, we only need to do one.
- removeMessages(0);
- updateWidgetUi();
- break;
- case 1: // We are being told to display an error message
- removeMessages(1);
- }
- }
- };
-
- /**
- * Registers new Widgets, for updating them when needed. UiUpdater sends UI update
- * messages.
- *
- * @param newWidgetIds
- * @param newContext
- * @param newAppWidgetManager
- */
- @TargetApi(3)
- protected static void registerWidgets(int[] newWidgetIds, Context newContext,
- AppWidgetManager newAppWidgetManager) {
- context = newContext;
- appWidgetManager = newAppWidgetManager;
-
- for (int newWidgetId : newWidgetIds) {
- widgetIds.add(new Integer(newWidgetId));
- }
- }
-
- /**
- * Unregisters deleted Widgets.
- *
- * @param newWidgetIds
- */
- protected static void unregisterWidgets(int[] newWidgetIds) {
-
- if (!widgetIds.isEmpty()) {
- for (int newWidgetId : newWidgetIds) {
- widgetIds.remove(new Integer(newWidgetId));
- }
- }
-
- }
-
- /**
- * Unregister all Widgets on the List.
- */
- protected static void unregisterAllWidgets() {
- widgetIds.clear();
- }
-
- /**
- * Start listening for UI Updates, to know, when updating the Widgets
- */
- protected static void registerAtUiUpdater() {
- // TODO: fix this part of the code
- // UiUpdater.registerClient(handler);
- }
-
- /**
- * Stop listening for UI Updates.
- */
- protected static void unregisterAtUiUpdater() {
- // TODO: fix this part of the code
- // UiUpdater.unregisterClient(handler);
- }
-
- /**
- * Updates all Widgets, when static UiUpdater (Observer Pattern) tells to.
- */
- private static void updateWidgetUi() {
-
- // tell all Widgets, to Update themselves. Right State is set in the onUpdate
- // handler.
- RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
- R.layout.ftp_widget);
-
- // set the right state, according to the FTP Server
- if (FTPServerService.isRunning()) {
- remoteViews.setViewVisibility(R.id.widget_button_on, View.VISIBLE);
- remoteViews.setViewVisibility(R.id.widget_button_off, View.GONE);
- } else {
- remoteViews.setViewVisibility(R.id.widget_button_on, View.GONE);
- remoteViews.setViewVisibility(R.id.widget_button_off, View.VISIBLE);
- }
-
- for (Integer widgetId : widgetIds) {
- appWidgetManager.updateAppWidget(widgetId.intValue(), remoteViews);
- }
-
- }
-
-}
+package org.swiftp;
+
+import java.util.HashSet;
+
+import android.annotation.TargetApi;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.widget.RemoteViews;
+
+/**
+ * This class maintains Widget Updates. It obtains information about UI Updates from
+ * UiUpdater.class. For starting getting UiUpdates, method {@link #registerAtUiUpdater()}
+ * have to be executed once.
+ *
+ * Why does this Class exist? This class exists, because I was not able to store
+ * {@link #handler} for every single Widget. There was a Problem with unregistering
+ * handlers, when a Widget was deleted.
+ */
+public class WidgetUiUpdater {
+ private static HashSet widgetIds = new HashSet();
+ private static Context context;
+ private static AppWidgetManager appWidgetManager;
+
+ @SuppressWarnings("unused")
+ private static Handler handler = new Handler() {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case 0: // We are being told to do a UI update
+ // If more than one UI update is queued up, we only need to do one.
+ removeMessages(0);
+ updateWidgetUi();
+ break;
+ case 1: // We are being told to display an error message
+ removeMessages(1);
+ }
+ }
+ };
+
+ /**
+ * Registers new Widgets, for updating them when needed. UiUpdater sends UI update
+ * messages.
+ *
+ * @param newWidgetIds
+ * @param newContext
+ * @param newAppWidgetManager
+ */
+ @TargetApi(3)
+ protected static void registerWidgets(int[] newWidgetIds, Context newContext,
+ AppWidgetManager newAppWidgetManager) {
+ context = newContext;
+ appWidgetManager = newAppWidgetManager;
+
+ for (int newWidgetId : newWidgetIds) {
+ widgetIds.add(new Integer(newWidgetId));
+ }
+ }
+
+ /**
+ * Unregisters deleted Widgets.
+ *
+ * @param newWidgetIds
+ */
+ protected static void unregisterWidgets(int[] newWidgetIds) {
+
+ if (!widgetIds.isEmpty()) {
+ for (int newWidgetId : newWidgetIds) {
+ widgetIds.remove(new Integer(newWidgetId));
+ }
+ }
+
+ }
+
+ /**
+ * Unregister all Widgets on the List.
+ */
+ protected static void unregisterAllWidgets() {
+ widgetIds.clear();
+ }
+
+ /**
+ * Start listening for UI Updates, to know, when updating the Widgets
+ */
+ protected static void registerAtUiUpdater() {
+ // TODO: fix this part of the code
+ // UiUpdater.registerClient(handler);
+ }
+
+ /**
+ * Stop listening for UI Updates.
+ */
+ protected static void unregisterAtUiUpdater() {
+ // TODO: fix this part of the code
+ // UiUpdater.unregisterClient(handler);
+ }
+
+ /**
+ * Updates all Widgets, when static UiUpdater (Observer Pattern) tells to.
+ */
+ private static void updateWidgetUi() {
+
+ // tell all Widgets, to Update themselves. Right State is set in the onUpdate
+ // handler.
+ RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
+ R.layout.ftp_widget);
+
+ // set the right state, according to the FTP Server
+ if (FTPServerService.isRunning()) {
+ remoteViews.setViewVisibility(R.id.widget_button_on, View.VISIBLE);
+ remoteViews.setViewVisibility(R.id.widget_button_off, View.GONE);
+ } else {
+ remoteViews.setViewVisibility(R.id.widget_button_on, View.GONE);
+ remoteViews.setViewVisibility(R.id.widget_button_off, View.VISIBLE);
+ }
+
+ for (Integer widgetId : widgetIds) {
+ appWidgetManager.updateAppWidget(widgetId.intValue(), remoteViews);
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/swiftp/gui/ServerPreferenceActivity.java b/src/main/java/org/swiftp/gui/ServerPreferenceActivity.java
index 6f0cd4379f9a8c3b8e8530d606831eb5207056c8..5a4fa8d65c941d51eb27abab26462eb68c2d2686 100644
--- a/src/main/java/org/swiftp/gui/ServerPreferenceActivity.java
+++ b/src/main/java/org/swiftp/gui/ServerPreferenceActivity.java
@@ -1,56 +1,56 @@
-package org.swiftp.gui;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import android.util.Log;
-
-import org.swiftp.R;
-
-/**
- * This is the main activity for swiftp, it enables the user to start the server service
- * and allows the users to change the settings.
- */
-public class ServerPreferenceActivity extends PreferenceActivity{
-
- private static String TAG = ServerPreferenceActivity.class.getSimpleName();
-
-
-
- public static void start(Context context) {
- Intent starter = new Intent(context, ServerPreferenceActivity.class);
- context.startActivity(starter);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.ftp_preferences);
- setContentView(R.layout.activity_preference);
-
-
- }
-
-
-
- @Override
- protected void onResume() {
- Log.v(TAG, "onResume");
- super.onResume();
-
- }
-
- @Override
- protected void onPause() {
- Log.v(TAG, "onPause");
- super.onPause();
-
- Log.v(TAG, "Unregistering the FTPServer actions");
-
-
- }
-
-
-
-}
+package org.swiftp.gui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.util.Log;
+
+import org.swiftp.R;
+
+/**
+ * This is the main activity for swiftp, it enables the user to start the server service
+ * and allows the users to change the settings.
+ */
+public class ServerPreferenceActivity extends PreferenceActivity{
+
+ private static String TAG = ServerPreferenceActivity.class.getSimpleName();
+
+
+
+ public static void start(Context context) {
+ Intent starter = new Intent(context, ServerPreferenceActivity.class);
+ context.startActivity(starter);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.ftp_preferences);
+ setContentView(R.layout.activity_preference);
+
+
+ }
+
+
+
+ @Override
+ protected void onResume() {
+ Log.v(TAG, "onResume");
+ super.onResume();
+
+ }
+
+ @Override
+ protected void onPause() {
+ Log.v(TAG, "onPause");
+ super.onPause();
+
+ Log.v(TAG, "Unregistering the FTPServer actions");
+
+
+ }
+
+
+
+}
diff --git a/src/main/java/org/swiftp/gui/ServerPreferenceFragment.java b/src/main/java/org/swiftp/gui/ServerPreferenceFragment.java
index ddff8f1c6d4e9c7e3cf3dbd056cfbe0053bfe5f5..432306c12ad5bdf6ce79851b741f4be7f89b6d8d 100644
--- a/src/main/java/org/swiftp/gui/ServerPreferenceFragment.java
+++ b/src/main/java/org/swiftp/gui/ServerPreferenceFragment.java
@@ -1,302 +1,308 @@
-package org.swiftp.gui;
-
-import android.annotation.TargetApi;
-import android.app.AlertDialog;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Environment;
-import android.preference.CheckBoxPreference;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import android.view.Gravity;
-import android.widget.Toast;
-
-import org.swiftp.FTPServerService;
-import org.swiftp.Globals;
-import org.swiftp.R;
-
-import java.io.File;
-import java.net.InetAddress;
-
-/**
- * Created by Hmei on 2017年06月07日.
- */
-
-@TargetApi(Build.VERSION_CODES.HONEYCOMB)
-public class ServerPreferenceFragment extends PreferenceFragment implements
- SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = "SPF";
- EditTextPreference mPassWordPref;
- /**
- * This receiver will check FTPServer.ACTION* messages and will update the button,
- * running_state, if the server is running and will also display at what url the
- * server is running.
- */
- BroadcastReceiver ftpServerReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.v(TAG, "FTPServerService action received: " + intent.getAction());
- CheckBoxPreference running_state = (CheckBoxPreference) findPreference("running_state");
- if (intent.getAction().equals(FTPServerService.ACTION_STARTED)) {
- running_state.setChecked(true);
- // Fill in the FTP server address
- InetAddress address = FTPServerService.getWifiAndApIp();
- if (address == null) {
- Log.v(TAG, "Unable to retreive wifi ip address");
- running_state.setSummary(R.string.cant_get_url);
- return;
- }
- String iptext = "ftp://" + address.getHostAddress() + ":"
- + FTPServerService.getPort() + "/";
- Resources resources = getResources();
- String summary = resources.getString(R.string.running_summary_started,
- iptext);
- running_state.setSummary(summary);
- } else if (intent.getAction().equals(FTPServerService.ACTION_STOPPED)) {
- running_state.setChecked(false);
- running_state.setSummary(R.string.running_summary_stopped);
- } else if (intent.getAction().equals(FTPServerService.ACTION_FAILEDTOSTART)) {
- running_state.setChecked(false);
- running_state.setSummary(R.string.running_summary_failed);
- }
- }
- };
-
- static private String transformPassword(String password) {
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(Globals
- .getContext());
- Resources res = Globals.getContext().getResources();
- boolean showPassword = res.getString(R.string.show_password_default).equals("true");
- showPassword = sp.getBoolean("show_password", showPassword);
- if (showPassword == true)
- return password;
- else {
- StringBuilder sb = new StringBuilder(password.length());
- for (int i = 0; i < password.length(); ++i) - sb.append('*'); - return sb.toString(); - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.ftp_preferences); - - final SharedPreferences settings = PreferenceManager - .getDefaultSharedPreferences(getActivity()); - Resources resources = getResources(); - - CheckBoxPreference running_state = (CheckBoxPreference) findPreference("running_state"); - running_state.setChecked(FTPServerService.isRunning()); - running_state.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - if ((Boolean) newValue) { - startServer(); - } else { - stopServer(); - } - return true; - } - }); - - EditTextPreference username_pref = (EditTextPreference) findPreference("username"); - username_pref.setSummary(settings.getString("username", - resources.getString(R.string.username_default))); - username_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String newUsername = (String) newValue; - if (preference.getSummary().equals(newUsername)) - return false; - if (!newUsername.matches("[a-zA-Z0-9]+")) { - Toast.makeText(getActivity(), - R.string.username_validation_error, Toast.LENGTH_LONG).show(); - return false; - } - preference.setSummary(newUsername); - stopServer(); - return true; - } - }); - - mPassWordPref = (EditTextPreference) findPreference("password"); - String password = resources.getString(R.string.password_default); - password = settings.getString("password", password); - mPassWordPref.setSummary(transformPassword(password)); - mPassWordPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String newPassword = (String) newValue; - if (!newPassword.matches("[a-zA-Z0-9]+")) { - Toast.makeText(getActivity(), - R.string.password_validation_error, Toast.LENGTH_LONG).show(); - return false; - } - preference.setSummary(transformPassword(newPassword)); - stopServer(); - return true; - } - }); - - EditTextPreference portnum_pref = (EditTextPreference) findPreference("portNum"); - portnum_pref.setSummary(settings.getString("portNum", - resources.getString(R.string.portnumber_default))); - portnum_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String newPortnumString = (String) newValue; - if (preference.getSummary().equals(newPortnumString)) - return false; - int portnum = 0; - try { - portnum = Integer.parseInt(newPortnumString); - } catch (Exception e) { - } - if (portnum <= 0 || 65535 < portnum) { - Toast.makeText(getActivity(), - R.string.port_validation_error, Toast.LENGTH_LONG).show(); - return false; - } - preference.setSummary(newPortnumString); - stopServer(); - return true; - } - }); - - EditTextPreference chroot_pref = (EditTextPreference) findPreference("chrootDir"); - chroot_pref.setSummary(settings.getString("chrootDir", - resources.getString(R.string.chroot_default))); - chroot_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - String newChroot = (String) newValue; - if (preference.getSummary().equals(newChroot)) - return false; - // now test the new chroot directory - File chrootTest = new File(newChroot); - if (!chrootTest.isDirectory() || !chrootTest.canRead()) - return false; - preference.setSummary(newChroot); - stopServer(); - return true; - } - }); - - final CheckBoxPreference wakelock_pref = (CheckBoxPreference) findPreference("stayAwake"); - wakelock_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { - @Override - public boolean onPreferenceChange(Preference preference, Object newValue) { - stopServer(); - return true; - } - }); - - Preference help = findPreference("help"); - help.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - new AlertDialog.Builder(getActivity()) - .setTitle(R.string.help_dlg_title) - .setMessage(R.string.help_dlg_message) - .setPositiveButton(getText(R.string.ok), null).show(); - return true; - } - }); - - Preference about = findPreference("about"); - about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - new AlertDialog.Builder(getActivity()) - .setTitle(R.string.about_dlg_title) - .setMessage(R.string.about_dlg_message) - .setPositiveButton(getText(R.string.ok), null).show(); - return true; - } - }); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sp, String key) { - if (key.equals("show_password")) { - Resources res = Globals.getContext().getResources(); - String password = res.getString(R.string.password_default); - password = sp.getString("password", password); - mPassWordPref.setSummary(transformPassword(password)); - } - } - - @Override - public void onResume() { - Log.v(TAG, "onResume"); - super.onResume(); - - // make this class listen for preference changes - getPreferenceScreen().getSharedPreferences() - .registerOnSharedPreferenceChangeListener(this); - - Log.v(TAG, "Registering the FTP server actions"); - IntentFilter filter = new IntentFilter(); - filter.addAction(FTPServerService.ACTION_STARTED); - filter.addAction(FTPServerService.ACTION_STOPPED); - filter.addAction(FTPServerService.ACTION_FAILEDTOSTART); - getActivity().registerReceiver(ftpServerReceiver, filter); - } - - @Override - public void onPause() { - Log.v(TAG, "onPause"); - super.onPause(); - - Log.v(TAG, "Unregistering the FTPServer actions"); - getActivity().unregisterReceiver(ftpServerReceiver); - - // unregister the listener - getPreferenceScreen().getSharedPreferences() - .unregisterOnSharedPreferenceChangeListener(this); - - } - - private void startServer() { - Context context = getActivity().getApplicationContext(); - Intent serverService = new Intent(context, FTPServerService.class); - if (!FTPServerService.isRunning()) { - warnIfNoExternalStorage(); - getActivity().startService(serverService); - } - } - - private void stopServer() { - Context context = getActivity().getApplicationContext(); - Intent serverService = new Intent(context, FTPServerService.class); - getActivity().stopService(serverService); - } - - /** - * Will check if the device contains external storage (sdcard) and display a warning - * for the user if there is no external storage. Nothing more. - */ - private void warnIfNoExternalStorage() { - String storageState = Environment.getExternalStorageState(); - if (!storageState.equals(Environment.MEDIA_MOUNTED)) { - Log.v(TAG, "Warning due to storage stat" + storageState); - Toast toast = Toast.makeText(getActivity(), R.string.storage_warning, - Toast.LENGTH_LONG); - toast.setGravity(Gravity.CENTER, 0, 0); - toast.show(); - } - } - - -} +package org.swiftp.gui; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.preference.CheckBoxPreference; +import android.preference.EditTextPreference; +import android.preference.Preference; +import android.preference.PreferenceFragment; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Gravity; +import android.widget.Toast; + +import org.swiftp.Defaults; +import org.swiftp.FTPServerService; +import org.swiftp.Globals; +import org.swiftp.R; + +import java.io.File; +import java.net.InetAddress; +import java.util.ArrayList; + +/** + * Created by Hmei on 2017-06-07. + */ + +@TargetApi(Build.VERSION_CODES.HONEYCOMB) +public class ServerPreferenceFragment extends PreferenceFragment implements + SharedPreferences.OnSharedPreferenceChangeListener { + private static final String TAG = "SPF"; + EditTextPreference mPassWordPref; + /** + * This receiver will check FTPServer.ACTION* messages and will update the button, + * running_state, if the server is running and will also display at what url the + * server is running. + */ + BroadcastReceiver ftpServerReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.v(TAG, "FTPServerService action received: " + intent.getAction()); + CheckBoxPreference running_state = (CheckBoxPreference) findPreference("running_state"); + if (intent.getAction().equals(FTPServerService.ACTION_STARTED)) { + running_state.setChecked(true); + // Fill in the FTP server address + String[] address = FTPServerService.getIpPortString(); + if (address == null) { + Log.v(TAG, "Unable to retreive wifi ip address"); + running_state.setSummary(R.string.cant_get_url); + return; + } + StringBuilder iptext = new StringBuilder(); + for(String ip : address){ + if(iptext.length()>0)
+ iptext.append(", ");
+ iptext.append(ip);
+ }
+ Resources resources = getResources();
+ String summary = resources.getString(R.string.running_summary_started,
+ iptext);
+ running_state.setSummary(summary);
+ } else if (intent.getAction().equals(FTPServerService.ACTION_STOPPED)) {
+ running_state.setChecked(false);
+ running_state.setSummary(R.string.running_summary_stopped);
+ } else if (intent.getAction().equals(FTPServerService.ACTION_FAILEDTOSTART)) {
+ running_state.setChecked(false);
+ running_state.setSummary(R.string.running_summary_failed);
+ }
+ }
+ };
+
+ static private String transformPassword(String password) {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(Globals
+ .getContext());
+ Resources res = Globals.getContext().getResources();
+ boolean showPassword = res.getString(R.string.show_password_default).equals("true");
+ showPassword = sp.getBoolean("show_password", showPassword);
+ if (showPassword == true)
+ return password;
+ else {
+ StringBuilder sb = new StringBuilder(password.length());
+ for (int i = 0; i < password.length(); ++i) + sb.append('*'); + return sb.toString(); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.ftp_preferences); + + final SharedPreferences settings = PreferenceManager + .getDefaultSharedPreferences(getActivity()); + Resources resources = getResources(); + + CheckBoxPreference running_state = (CheckBoxPreference) findPreference("running_state"); + running_state.setChecked(FTPServerService.isRunning()); + running_state.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if ((Boolean) newValue) { + startServer(); + } else { + stopServer(); + } + return true; + } + }); + + EditTextPreference username_pref = (EditTextPreference) findPreference("username"); + username_pref.setSummary(settings.getString("username", + resources.getString(R.string.username_default))); + username_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String newUsername = (String) newValue; + if (preference.getSummary().equals(newUsername)) + return false; + if (!newUsername.matches("[a-zA-Z0-9]+")) { + Toast.makeText(getActivity(), + R.string.username_validation_error, Toast.LENGTH_LONG).show(); + return false; + } + preference.setSummary(newUsername); + stopServer(); + return true; + } + }); + + mPassWordPref = (EditTextPreference) findPreference("password"); + String password = resources.getString(R.string.password_default); + password = settings.getString("password", password); + mPassWordPref.setSummary(transformPassword(password)); + mPassWordPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String newPassword = (String) newValue; + if (!newPassword.matches("[a-zA-Z0-9]+")) { + Toast.makeText(getActivity(), + R.string.password_validation_error, Toast.LENGTH_LONG).show(); + return false; + } + preference.setSummary(transformPassword(newPassword)); + stopServer(); + return true; + } + }); + + EditTextPreference portnum_pref = (EditTextPreference) findPreference("portNum"); + portnum_pref.setSummary(settings.getString("portNum", + getString(R.string.portnumber_default))); + portnum_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String newPortnumString = (String) newValue; + if (preference.getSummary().equals(newPortnumString)) + return false; + int portnum = 0; + try { + portnum = Integer.parseInt(newPortnumString); + } catch (Exception e) { + } + if (portnum <= 0 || 65535 < portnum) { + Toast.makeText(getActivity(), + R.string.port_validation_error, Toast.LENGTH_LONG).show(); + return false; + } + preference.setSummary(newPortnumString); + stopServer(); + return true; + } + }); + + EditTextPreference chroot_pref = (EditTextPreference) findPreference("chrootDir"); + chroot_pref.setSummary(settings.getString("chrootDir", + resources.getString(R.string.chroot_default))); + chroot_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String newChroot = (String) newValue; + if (preference.getSummary().equals(newChroot)) + return false; + // now test the new chroot directory + File chrootTest = new File(newChroot); + if (!chrootTest.isDirectory() || !chrootTest.canRead()) + return false; + preference.setSummary(newChroot); + stopServer(); + return true; + } + }); + + final CheckBoxPreference wakelock_pref = (CheckBoxPreference) findPreference("stayAwake"); + wakelock_pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + stopServer(); + return true; + } + }); + + Preference help = findPreference("help"); + help.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.help_dlg_title) + .setMessage(R.string.help_dlg_message) + .setPositiveButton(getText(R.string.ok), null).show(); + return true; + } + }); + + Preference about = findPreference("about"); + about.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.about_dlg_title) + .setMessage(R.string.about_dlg_message) + .setPositiveButton(getText(R.string.ok), null).show(); + return true; + } + }); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sp, String key) { + if (key.equals("show_password")) { + Resources res = Globals.getContext().getResources(); + String password = res.getString(R.string.password_default); + password = sp.getString("password", password); + mPassWordPref.setSummary(transformPassword(password)); + } + } + + @Override + public void onResume() { + Log.v(TAG, "onResume"); + super.onResume(); + + // make this class listen for preference changes + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); + + Log.v(TAG, "Registering the FTP server actions"); + IntentFilter filter = new IntentFilter(); + filter.addAction(FTPServerService.ACTION_STARTED); + filter.addAction(FTPServerService.ACTION_STOPPED); + filter.addAction(FTPServerService.ACTION_FAILEDTOSTART); + getActivity().registerReceiver(ftpServerReceiver, filter); + } + + @Override + public void onPause() { + Log.v(TAG, "onPause"); + super.onPause(); + + Log.v(TAG, "Unregistering the FTPServer actions"); + getActivity().unregisterReceiver(ftpServerReceiver); + + // unregister the listener + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); + + } + + private void startServer() { + Context context = getActivity().getApplicationContext(); + Intent serverService = new Intent(context, FTPServerService.class); + if (!FTPServerService.isRunning()) { + warnIfNoExternalStorage(); + getActivity().startService(serverService); + } + } + + private void stopServer() { + Context context = getActivity().getApplicationContext(); + Intent serverService = new Intent(context, FTPServerService.class); + getActivity().stopService(serverService); + } + + /** + * Will check if the device contains external storage (sdcard) and display a warning + * for the user if there is no external storage. Nothing more. + */ + private void warnIfNoExternalStorage() { + String storageState = Environment.getExternalStorageState(); + if (!storageState.equals(Environment.MEDIA_MOUNTED)) { + Log.v(TAG, "Warning due to storage stat" + storageState); + Toast toast = Toast.makeText(getActivity(), R.string.storage_warning, + Toast.LENGTH_LONG); + toast.setGravity(Gravity.CENTER, 0, 0); + toast.show(); + } + } + + +} diff --git a/src/main/java/org/swiftp/server/Account.java b/src/main/java/org/swiftp/server/Account.java index c56731e7b9fc5f5214ca3760fa4f9f3fa9388dab..28e43374f281553b4448315f473318855b8aa7c2 100644 --- a/src/main/java/org/swiftp/server/Account.java +++ b/src/main/java/org/swiftp/server/Account.java @@ -1,34 +1,34 @@ -/* -Copyright 2009 David Revell - -This file is part of SwiFTP. - -SwiFTP is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -SwiFTP is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-public class Account {
- protected String username = null;
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+public class Account {
+ protected String username = null;
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdAPPE.java b/src/main/java/org/swiftp/server/CmdAPPE.java
index 108f2cb05953145b2523b76326916f41b9940d63..045f4046ef538c912a5e5047197496a003ad3c90 100644
--- a/src/main/java/org/swiftp/server/CmdAPPE.java
+++ b/src/main/java/org/swiftp/server/CmdAPPE.java
@@ -1,35 +1,35 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-
-public class CmdAPPE extends CmdAbstractStore implements Runnable {
- protected String input;
-
- public CmdAPPE(SessionThread sessionThread, String input) {
- super(sessionThread, CmdAPPE.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- doStorOrAppe(getParameter(input), true);
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+
+public class CmdAPPE extends CmdAbstractStore implements Runnable {
+ protected String input;
+
+ public CmdAPPE(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdAPPE.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ doStorOrAppe(getParameter(input), true);
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdAbstractListing.java b/src/main/java/org/swiftp/server/CmdAbstractListing.java
index 9e8fedc99a274eca1a28319e946809e72c85145d..4ff625110566becce9448f16ed5297a732318c08 100644
--- a/src/main/java/org/swiftp/server/CmdAbstractListing.java
+++ b/src/main/java/org/swiftp/server/CmdAbstractListing.java
@@ -1,94 +1,94 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-/*
- * Since the FTP verbs LIST and NLST do very similar things related to listing
- * directory contents, the common tasks that they share have been factored
- * out into this abstract class. Both CmdLIST and CmdNLST inherit from this
- * class.
- */
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import org.swiftp.MyLog;
-
-import android.util.Log;
-
-public abstract class CmdAbstractListing extends FtpCmd {
- protected static MyLog staticLog = new MyLog(CmdLIST.class.toString());
-
- public CmdAbstractListing(SessionThread sessionThread, String input) {
- super(sessionThread, CmdAbstractListing.class.toString());
- }
-
- abstract String makeLsString(File file);
-
- // Creates a directory listing by finding the contents of the directory,
- // calling makeLsString on each file, and concatenating the results.
- // Returns an error string if failure, returns null on success. May be
- // called by CmdLIST or CmdNLST, since they each override makeLsString
- // in a different way.
- public String listDirectory(StringBuilder response, File dir) {
- if(!dir.isDirectory()) {
- return "500 Internal error, listDirectory on non-directory\r\n";
- }
- myLog.l(Log.DEBUG, "Listing directory: " + dir.toString());
-
- // Get a listing of all files and directories in the path
- File[] entries = dir.listFiles();
- if(entries == null) {
- return "500 Couldn't list directory. Check config and mount status.\r\n";
- }
- myLog.l(Log.DEBUG, "Dir len " + entries.length);
- for(File entry : entries) {
- String curLine = makeLsString(entry);
- if(curLine != null) {
- response.append(curLine);
- }
- }
- return null;
- }
-
- // Send the directory listing over the data socket. Used by CmdLIST and
- // CmdNLST.
- // Returns an error string on failure, or returns null if successful.
- protected String sendListing(String listing) {
- if(sessionThread.startUsingDataSocket()) {
- myLog.l(Log.DEBUG, "LIST/NLST done making socket");
- } else {
- sessionThread.closeDataSocket();
- return "425 Error opening data socket\r\n";
- }
- String mode = sessionThread.isBinaryMode() ? "BINARY" : "ASCII";
- sessionThread.writeString(
- "150 Opening "+mode+" mode data connection for file list\r\n");
- myLog.l(Log.DEBUG, "Sent code 150, sending listing string now");
- if(!sessionThread.sendViaDataSocket(listing)) {
- myLog.l(Log.DEBUG, "sendViaDataSocket failure");
- sessionThread.closeDataSocket();
- return "426 Data socket or network error\r\n";
- }
- sessionThread.closeDataSocket();
- myLog.l(Log.DEBUG, "Listing sendViaDataSocket success");
- sessionThread.writeString("226 Data transmission OK\r\n");
- return null;
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+/*
+ * Since the FTP verbs LIST and NLST do very similar things related to listing
+ * directory contents, the common tasks that they share have been factored
+ * out into this abstract class. Both CmdLIST and CmdNLST inherit from this
+ * class.
+ */
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import org.swiftp.MyLog;
+
+import android.util.Log;
+
+public abstract class CmdAbstractListing extends FtpCmd {
+ protected static MyLog staticLog = new MyLog(CmdLIST.class.toString());
+
+ public CmdAbstractListing(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdAbstractListing.class.toString());
+ }
+
+ abstract String makeLsString(File file);
+
+ // Creates a directory listing by finding the contents of the directory,
+ // calling makeLsString on each file, and concatenating the results.
+ // Returns an error string if failure, returns null on success. May be
+ // called by CmdLIST or CmdNLST, since they each override makeLsString
+ // in a different way.
+ public String listDirectory(StringBuilder response, File dir) {
+ if(!dir.isDirectory()) {
+ return "500 Internal error, listDirectory on non-directory\r\n";
+ }
+ myLog.l(Log.DEBUG, "Listing directory: " + dir.toString());
+
+ // Get a listing of all files and directories in the path
+ File[] entries = dir.listFiles();
+ if(entries == null) {
+ return "500 Couldn't list directory. Check config and mount status.\r\n";
+ }
+ myLog.l(Log.DEBUG, "Dir len " + entries.length);
+ for(File entry : entries) {
+ String curLine = makeLsString(entry);
+ if(curLine != null) {
+ response.append(curLine);
+ }
+ }
+ return null;
+ }
+
+ // Send the directory listing over the data socket. Used by CmdLIST and
+ // CmdNLST.
+ // Returns an error string on failure, or returns null if successful.
+ protected String sendListing(String listing) {
+ if(sessionThread.startUsingDataSocket()) {
+ myLog.l(Log.DEBUG, "LIST/NLST done making socket");
+ } else {
+ sessionThread.closeDataSocket();
+ return "425 Error opening data socket\r\n";
+ }
+ String mode = sessionThread.isBinaryMode() ? "BINARY" : "ASCII";
+ sessionThread.writeString(
+ "150 Opening "+mode+" mode data connection for file list\r\n");
+ myLog.l(Log.DEBUG, "Sent code 150, sending listing string now");
+ if(!sessionThread.sendViaDataSocket(listing)) {
+ myLog.l(Log.DEBUG, "sendViaDataSocket failure");
+ sessionThread.closeDataSocket();
+ return "426 Data socket or network error\r\n";
+ }
+ sessionThread.closeDataSocket();
+ myLog.l(Log.DEBUG, "Listing sendViaDataSocket success");
+ sessionThread.writeString("226 Data transmission OK\r\n");
+ return null;
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdAbstractStore.java b/src/main/java/org/swiftp/server/CmdAbstractStore.java
index df193bd19dac36ec4d7bc65a15d4e5d5400a5999..cc7465a69dbe93255c48d4167852b35632e892d5 100644
--- a/src/main/java/org/swiftp/server/CmdAbstractStore.java
+++ b/src/main/java/org/swiftp/server/CmdAbstractStore.java
@@ -1,213 +1,217 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-/**
- * Since STOR and APPE are essentially identical except for append vs truncate,
- * the common code is in this class, and inherited by CmdSTOR and CmdAPPE.
- */
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import org.swiftp.Defaults;
-import org.swiftp.Util;
-
-import android.util.Log;
-
-
-abstract public class CmdAbstractStore extends FtpCmd {
- public static final String message = "TEMPLATE!!";
-
- public CmdAbstractStore(SessionThread sessionThread, String input) {
- super(sessionThread, CmdAbstractStore.class.toString());
- }
-
- public void doStorOrAppe(String param, boolean append) {
- myLog.l(Log.DEBUG, "STOR/APPE executing with append=" + append);
- File storeFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
-
- String errString = null;
- FileOutputStream out = null;
- //DedicatedWriter dedicatedWriter = null;
-// int origPriority = Thread.currentThread().getPriority();
-// myLog.l(Log.DEBUG, "STOR original priority: " + origPriority);
- storing: {
- // Get a normalized absolute path for the desired file
- if(violatesChroot(storeFile)) {
- errString = "550 Invalid name or chroot violation\r\n";
- break storing;
- }
- if(storeFile.isDirectory()) {
- errString = "451 Can't overwrite a directory\r\n";
- break storing;
- }
-
- try {
- if(storeFile.exists()) {
- if(!append) {
- if(!storeFile.delete()) {
- errString = "451 Couldn't truncate file\r\n";
- break storing;
- }
- // Notify other apps that we just deleted a file
- Util.deletedFileNotify(storeFile.getPath());
- }
- }
- out = new FileOutputStream(storeFile, append);
- } catch(FileNotFoundException e) {
- try {
- errString = "451 Couldn't open file \"" + param + "\" aka \"" +
- storeFile.getCanonicalPath() + "\" for writing\r\n";
- } catch (IOException io_e) {
- errString = "451 Couldn't open file, nested exception\r\n";
- }
- break storing;
- }
- if(!sessionThread.startUsingDataSocket()) {
- errString = "425 Couldn't open data socket\r\n";
- break storing;
- }
- myLog.l(Log.DEBUG, "Data socket ready");
- sessionThread.writeString("150 Data socket ready\r\n");
- byte[] buffer = new byte[Defaults.getDataChunkSize()];
- //dedicatedWriter = new DedicatedWriter(out);
- //dedicatedWriter.start(); // start the writer thread executing
- //myLog.l(Log.DEBUG, "Started DedicatedWriter");
- int numRead;
-// Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
-// int newPriority = Thread.currentThread().getPriority();
-// myLog.l(Log.DEBUG, "New STOR prio: " + newPriority);
- if(sessionThread.isBinaryMode() ) {
- myLog.d("Mode is binary");
- } else {
- myLog.d("Mode is ascii");
- }
- while(true) {
- /*if(dedicatedWriter.checkErrorFlag()) {
- errString = "451 File IO problem\r\n";
- break storing;
- }*/
- switch(numRead = sessionThread.receiveFromDataSocket(buffer)) {
- case -1:
- myLog.l(Log.DEBUG, "Returned from final read");
- // We're finished reading
- break storing;
- case 0:
- errString = "426 Couldn't receive data\r\n";
- break storing;
- case -2:
- errString = "425 Could not connect data socket\r\n";
- break storing;
- default:
-// myLog.d("Read " + numRead + " bytes from socket");
- try {
- //myLog.l(Log.DEBUG, "Enqueueing buffer of " + numRead);
- //dedicatedWriter.enqueueBuffer(buffer, numRead);
- if(sessionThread.isBinaryMode()) {
- out.write(buffer, 0, numRead);
- } else {
- // ASCII mode, substitute \r\n to \n
- int startPos=0, endPos;
- for(endPos = 0; endPos < numRead; endPos++ ) { - if(buffer[endPos] == '\r') { - // Our hacky method is to drop all \r - out.write(buffer, startPos, endPos-startPos); - startPos = endPos+1; - } - } - // Write last part of buffer as long as there was something - // left after handling the last \r - if(startPos < numRead) { - out.write(buffer, startPos, endPos-startPos); - } - } - - // Attempted bugfix for transfer stalls. Reopen file periodically. - //bytesSinceReopen += numRead; - //if(bytesSinceReopen>= Defaults.bytes_between_reopen &&
- // Defaults.do_reopen_hack) {
- // myLog.d("Closing and reopening file: " + storeFile);
- // out.close();
- // out = new FileOutputStream(storeFile, true/*append*/);
- // bytesSinceReopen = 0;
- //}
-
- // Attempted bugfix for transfer stalls. Flush file periodically.
- //bytesSinceFlush += numRead;
- //if(bytesSinceFlush>= Defaults.bytes_between_flush &&
- // Defaults.do_flush_hack) {
- // myLog.d("Flushing: " + storeFile);
- // out.flush();
- // bytesSinceFlush = 0;
- //}
-
- // If this transfer fails, a later APPEND operation might be
- // received. In that case, we will need to have flushed the
- // previous writes in order for the append to work. The
- // filesystem on my G1 doesn't seem to recognized unflushed
- // data when appending.
- out.flush();
-
- } catch (IOException e) {
- errString = "451 File IO problem. Device might be full.\r\n";
- myLog.d("Exception while storing: " + e);
- myLog.d("Message: " + e.getMessage());
- myLog.d("Stack trace: ");
- StackTraceElement[] traceElems = e.getStackTrace();
- for(StackTraceElement elem : traceElems) {
- myLog.d(elem.toString());
- }
- break storing;
- }
- break;
- }
- }
- }
-// // Clean up the dedicated writer thread
-// if(dedicatedWriter != null) {
-// dedicatedWriter.exit(); // set its exit flag
-// dedicatedWriter.interrupt(); // make sure it wakes up to process the flag
-// }
-// Thread.currentThread().setPriority(origPriority);
- try {
-// if(dedicatedWriter != null) {
-// dedicatedWriter.exit();
-// }
- if(out != null) {
- out.close();
- }
- } catch (IOException e) {}
-
- if(errString != null) {
- myLog.l(Log.INFO, "STOR error: " + errString.trim());
- sessionThread.writeString(errString);
- } else {
- sessionThread.writeString("226 Transmission complete\r\n");
- // Notify the music player (and possibly others) that a few file has
- // been uploaded.
- Util.newFileNotify(storeFile.getPath());
- }
- sessionThread.closeDataSocket();
- myLog.l(Log.DEBUG, "STOR finished");
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+/**
+ * Since STOR and APPE are essentially identical except for append vs truncate,
+ * the common code is in this class, and inherited by CmdSTOR and CmdAPPE.
+ */
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.swiftp.Defaults;
+import org.swiftp.Globals;
+import org.swiftp.Util;
+
+import android.util.Log;
+
+import util.DocumentUtil;
+import util.FileUtil;
+
+
+abstract public class CmdAbstractStore extends FtpCmd {
+ public static final String message = "TEMPLATE!!";
+
+ public CmdAbstractStore(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdAbstractStore.class.toString());
+ }
+
+ public void doStorOrAppe(String param, boolean append) {
+ myLog.l(Log.DEBUG, "STOR/APPE executing with append=" + append);
+ File storeFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
+
+ String errString = null;
+ FileOutputStream out = null;
+ //DedicatedWriter dedicatedWriter = null;
+// int origPriority = Thread.currentThread().getPriority();
+// myLog.l(Log.DEBUG, "STOR original priority: " + origPriority);
+ storing: {
+ // Get a normalized absolute path for the desired file
+ if(violatesChroot(storeFile)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ break storing;
+ }
+ if(storeFile.isDirectory()) {
+ errString = "451 Can't overwrite a directory\r\n";
+ break storing;
+ }
+
+ try {
+ if(storeFile.exists()) {
+ if(!append) {
+ if(!storeFile.delete()) {
+ errString = "451 Couldn't truncate file\r\n";
+ break storing;
+ }
+ // Notify other apps that we just deleted a file
+ Util.deletedFileNotify(storeFile.getPath());
+ }
+ }
+ out = FileUtil.getFileOutputStream(storeFile,append);
+ } catch(Exception e) {
+ try {
+ errString = "451 Couldn't open file \"" + param + "\" aka \"" +
+ storeFile.getCanonicalPath() + "\" for writing\r\n";
+ } catch (IOException io_e) {
+ errString = "451 Couldn't open file, nested exception\r\n";
+ }
+ break storing;
+ }
+ if(!sessionThread.startUsingDataSocket()) {
+ errString = "425 Couldn't open data socket\r\n";
+ break storing;
+ }
+ myLog.l(Log.DEBUG, "Data socket ready");
+ sessionThread.writeString("150 Data socket ready\r\n");
+ byte[] buffer = new byte[Defaults.getDataChunkSize()];
+ //dedicatedWriter = new DedicatedWriter(out);
+ //dedicatedWriter.start(); // start the writer thread executing
+ //myLog.l(Log.DEBUG, "Started DedicatedWriter");
+ int numRead;
+// Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
+// int newPriority = Thread.currentThread().getPriority();
+// myLog.l(Log.DEBUG, "New STOR prio: " + newPriority);
+ if(sessionThread.isBinaryMode() ) {
+ myLog.d("Mode is binary");
+ } else {
+ myLog.d("Mode is ascii");
+ }
+ while(true) {
+ /*if(dedicatedWriter.checkErrorFlag()) {
+ errString = "451 File IO problem\r\n";
+ break storing;
+ }*/
+ switch(numRead = sessionThread.receiveFromDataSocket(buffer)) {
+ case -1:
+ myLog.l(Log.DEBUG, "Returned from final read");
+ // We're finished reading
+ break storing;
+ case 0:
+ errString = "426 Couldn't receive data\r\n";
+ break storing;
+ case -2:
+ errString = "425 Could not connect data socket\r\n";
+ break storing;
+ default:
+// myLog.d("Read " + numRead + " bytes from socket");
+ try {
+ //myLog.l(Log.DEBUG, "Enqueueing buffer of " + numRead);
+ //dedicatedWriter.enqueueBuffer(buffer, numRead);
+ if(sessionThread.isBinaryMode()) {
+ out.write(buffer, 0, numRead);
+ } else {
+ // ASCII mode, substitute \r\n to \n
+ int startPos=0, endPos;
+ for(endPos = 0; endPos < numRead; endPos++ ) { + if(buffer[endPos] == '\r') { + // Our hacky method is to drop all \r + out.write(buffer, startPos, endPos-startPos); + startPos = endPos+1; + } + } + // Write last part of buffer as long as there was something + // left after handling the last \r + if(startPos < numRead) { + out.write(buffer, startPos, endPos-startPos); + } + } + + // Attempted bugfix for transfer stalls. Reopen file periodically. + //bytesSinceReopen += numRead; + //if(bytesSinceReopen>= Defaults.bytes_between_reopen &&
+ // Defaults.do_reopen_hack) {
+ // myLog.d("Closing and reopening file: " + storeFile);
+ // out.close();
+ // out = new FileOutputStream(storeFile, true/*append*/);
+ // bytesSinceReopen = 0;
+ //}
+
+ // Attempted bugfix for transfer stalls. Flush file periodically.
+ //bytesSinceFlush += numRead;
+ //if(bytesSinceFlush>= Defaults.bytes_between_flush &&
+ // Defaults.do_flush_hack) {
+ // myLog.d("Flushing: " + storeFile);
+ // out.flush();
+ // bytesSinceFlush = 0;
+ //}
+
+ // If this transfer fails, a later APPEND operation might be
+ // received. In that case, we will need to have flushed the
+ // previous writes in order for the append to work. The
+ // filesystem on my G1 doesn't seem to recognized unflushed
+ // data when appending.
+ out.flush();
+
+ } catch (IOException e) {
+ errString = "451 File IO problem. Device might be full.\r\n";
+ myLog.d("Exception while storing: " + e);
+ myLog.d("Message: " + e.getMessage());
+ myLog.d("Stack trace: ");
+ StackTraceElement[] traceElems = e.getStackTrace();
+ for(StackTraceElement elem : traceElems) {
+ myLog.d(elem.toString());
+ }
+ break storing;
+ }
+ break;
+ }
+ }
+ }
+// // Clean up the dedicated writer thread
+// if(dedicatedWriter != null) {
+// dedicatedWriter.exit(); // set its exit flag
+// dedicatedWriter.interrupt(); // make sure it wakes up to process the flag
+// }
+// Thread.currentThread().setPriority(origPriority);
+ try {
+// if(dedicatedWriter != null) {
+// dedicatedWriter.exit();
+// }
+ if(out != null) {
+ out.close();
+ }
+ } catch (IOException e) {}
+
+ if(errString != null) {
+ myLog.l(Log.INFO, "STOR error: " + errString.trim());
+ sessionThread.writeString(errString);
+ } else {
+ sessionThread.writeString("226 Transmission complete\r\n");
+ // Notify the music player (and possibly others) that a few file has
+ // been uploaded.
+ Util.newFileNotify(storeFile.getPath());
+ }
+ sessionThread.closeDataSocket();
+ myLog.l(Log.DEBUG, "STOR finished");
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdCDUP.java b/src/main/java/org/swiftp/server/CmdCDUP.java
index bc7ee35dd5255ee86dfe7cf09b99489c58609751..eb1602c6cc7f6bda34b99d6e3dfd77be2abe08cd 100644
--- a/src/main/java/org/swiftp/server/CmdCDUP.java
+++ b/src/main/java/org/swiftp/server/CmdCDUP.java
@@ -1,76 +1,76 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-import java.io.IOException;
-
-import android.util.Log;
-
-public class CmdCDUP extends FtpCmd implements Runnable {
- protected String input;
-
- public CmdCDUP(SessionThread sessionThread, String input) {
- super(sessionThread, CmdCDUP.class.toString());
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "CDUP executing");
- File newDir;
- String errString = null;
- mainBlock: {
- File workingDir = sessionThread.getWorkingDir();
- newDir = workingDir.getParentFile();
- if(newDir == null) {
- errString = "550 Current dir cannot find parent\r\n";
- break mainBlock;
- }
- // Ensure the new path does not violate the chroot restriction
- if(violatesChroot(newDir)) {
- errString = "550 Invalid name or chroot violation\r\n";
- break mainBlock;
- }
-
- try {
- newDir = newDir.getCanonicalFile();
- if(!newDir.isDirectory()) {
- errString = "550 Can't CWD to invalid directory\r\n";
- break mainBlock;
- } else if(newDir.canRead()) {
- sessionThread.setWorkingDir(newDir);
- } else {
- errString = "550 That path is inaccessible\r\n";
- break mainBlock;
- }
- } catch(IOException e) {
- errString = "550 Invalid path\r\n";
- break mainBlock;
- }
- }
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.i("CDUP error: " + errString);
- } else {
- sessionThread.writeString("200 CDUP successful\r\n");
- myLog.l(Log.DEBUG, "CDUP success");
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+import java.io.IOException;
+
+import android.util.Log;
+
+public class CmdCDUP extends FtpCmd implements Runnable {
+ protected String input;
+
+ public CmdCDUP(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdCDUP.class.toString());
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "CDUP executing");
+ File newDir;
+ String errString = null;
+ mainBlock: {
+ File workingDir = sessionThread.getWorkingDir();
+ newDir = workingDir.getParentFile();
+ if(newDir == null) {
+ errString = "550 Current dir cannot find parent\r\n";
+ break mainBlock;
+ }
+ // Ensure the new path does not violate the chroot restriction
+ if(violatesChroot(newDir)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ break mainBlock;
+ }
+
+ try {
+ newDir = newDir.getCanonicalFile();
+ if(!newDir.isDirectory()) {
+ errString = "550 Can't CWD to invalid directory\r\n";
+ break mainBlock;
+ } else if(newDir.canRead()) {
+ sessionThread.setWorkingDir(newDir);
+ } else {
+ errString = "550 That path is inaccessible\r\n";
+ break mainBlock;
+ }
+ } catch(IOException e) {
+ errString = "550 Invalid path\r\n";
+ break mainBlock;
+ }
+ }
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.i("CDUP error: " + errString);
+ } else {
+ sessionThread.writeString("200 CDUP successful\r\n");
+ myLog.l(Log.DEBUG, "CDUP success");
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdCWD.java b/src/main/java/org/swiftp/server/CmdCWD.java
index a13e8ca22922e0cc29c6af752cf252728102e0e1..2b5777938a674ccbf323120bd659b66f121033d0 100644
--- a/src/main/java/org/swiftp/server/CmdCWD.java
+++ b/src/main/java/org/swiftp/server/CmdCWD.java
@@ -1,69 +1,69 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-import java.io.IOException;
-
-import android.util.Log;
-
-public class CmdCWD extends FtpCmd implements Runnable {
- protected String input;
-
- public CmdCWD(SessionThread sessionThread, String input) {
- super(sessionThread, CmdCWD.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "CWD executing");
- String param = getParameter(input);
- File newDir;
- String errString = null;
- mainblock: {
- newDir = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
-
- // Ensure the new path does not violate the chroot restriction
- if(violatesChroot(newDir)) {
- errString = "550 Invalid name or chroot violation\r\n";
- sessionThread.writeString(errString);
- myLog.l(Log.INFO, errString);
- break mainblock;
- }
-
- try {
- newDir = newDir.getCanonicalFile();
- if(!newDir.isDirectory()) {
- sessionThread.writeString("550 Can't CWD to invalid directory\r\n");
- } else if(newDir.canRead()) {
- sessionThread.setWorkingDir(newDir);
- sessionThread.writeString("250 CWD successful\r\n");
- } else {
- sessionThread.writeString("550 That path is inaccessible\r\n");
- }
- } catch(IOException e) {
- sessionThread.writeString("550 Invalid path\r\n");
- break mainblock;
- }
- }
- myLog.l(Log.DEBUG, "CWD complete");
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+import java.io.IOException;
+
+import android.util.Log;
+
+public class CmdCWD extends FtpCmd implements Runnable {
+ protected String input;
+
+ public CmdCWD(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdCWD.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "CWD executing");
+ String param = getParameter(input);
+ File newDir;
+ String errString = null;
+ mainblock: {
+ newDir = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
+
+ // Ensure the new path does not violate the chroot restriction
+ if(violatesChroot(newDir)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ sessionThread.writeString(errString);
+ myLog.l(Log.INFO, errString);
+ break mainblock;
+ }
+
+ try {
+ newDir = newDir.getCanonicalFile();
+ if(!newDir.isDirectory()) {
+ sessionThread.writeString("550 Can't CWD to invalid directory\r\n");
+ } else if(newDir.canRead()) {
+ sessionThread.setWorkingDir(newDir);
+ sessionThread.writeString("250 CWD successful\r\n");
+ } else {
+ sessionThread.writeString("550 That path is inaccessible\r\n");
+ }
+ } catch(IOException e) {
+ sessionThread.writeString("550 Invalid path\r\n");
+ break mainblock;
+ }
+ }
+ myLog.l(Log.DEBUG, "CWD complete");
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdDELE.java b/src/main/java/org/swiftp/server/CmdDELE.java
index 1613b6863009a310f94ed9896fabe446c060bcad..e17d798336ca5cc5f4298f9497820739a2c4c18e 100644
--- a/src/main/java/org/swiftp/server/CmdDELE.java
+++ b/src/main/java/org/swiftp/server/CmdDELE.java
@@ -1,60 +1,64 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import org.swiftp.Util;
-
-import android.util.Log;
-
-public class CmdDELE extends FtpCmd implements Runnable {
- protected String input;
-
- public CmdDELE(SessionThread sessionThread, String input) {
- super(sessionThread, CmdDELE.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.l(Log.INFO, "DELE executing");
- String param = getParameter(input);
- File storeFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
- String errString = null;
- if(violatesChroot(storeFile)) {
- errString = "550 Invalid name or chroot violation\r\n";
- } else if(storeFile.isDirectory()) {
- errString = "550 Can't DELE a directory\r\n";
- } else if(!storeFile.delete()) {
- errString = "450 Error deleting file\r\n";
- }
-
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.l(Log.INFO, "DELE failed: " + errString.trim());
- } else {
- sessionThread.writeString("250 File successfully deleted\r\n");
- Util.deletedFileNotify(storeFile.getPath());
- }
- myLog.l(Log.INFO, "DELE finished");
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import org.swiftp.Globals;
+import org.swiftp.Util;
+
+import android.util.Log;
+
+import util.DocumentUtil;
+import util.FileUtil;
+
+public class CmdDELE extends FtpCmd implements Runnable {
+ protected String input;
+
+ public CmdDELE(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdDELE.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.INFO, "DELE executing");
+ String param = getParameter(input);
+ File storeFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
+ String errString = null;
+ if(violatesChroot(storeFile)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ } else if(storeFile.isDirectory()) {
+ errString = "550 Can't DELE a directory\r\n";
+ } else if(!FileUtil.delete(storeFile)) {
+ errString = "450 Error deleting file\r\n";
+ }
+
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.l(Log.INFO, "DELE failed: " + errString.trim());
+ } else {
+ sessionThread.writeString("250 File successfully deleted\r\n");
+ Util.deletedFileNotify(storeFile.getPath());
+ }
+ myLog.l(Log.INFO, "DELE finished");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdFEAT.java b/src/main/java/org/swiftp/server/CmdFEAT.java
index b3294a3d842a2e7d7e142b12479d192f104f98b8..f3943f55fa0d4f98a977c4ddecaf010d316d1220 100644
--- a/src/main/java/org/swiftp/server/CmdFEAT.java
+++ b/src/main/java/org/swiftp/server/CmdFEAT.java
@@ -1,40 +1,40 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import android.util.Log;
-
-public class CmdFEAT extends FtpCmd implements Runnable {
- public static final String message = "TEMPLATE!!";
-
- public CmdFEAT(SessionThread sessionThread, String input) {
- super(sessionThread, CmdFEAT.class.toString());
- }
-
- @Override
- public void run() {
- //sessionThread.writeString("211 No extended features\r\n");
- sessionThread.writeString("211-Features supported\r\n");
- sessionThread.writeString(" UTF8\r\n"); // advertise UTF8 support (fixes bug 14)
- sessionThread.writeString("211 End\r\n");
- myLog.l(Log.DEBUG, "Gave FEAT response");
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import android.util.Log;
+
+public class CmdFEAT extends FtpCmd implements Runnable {
+ public static final String message = "TEMPLATE!!";
+
+ public CmdFEAT(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdFEAT.class.toString());
+ }
+
+ @Override
+ public void run() {
+ //sessionThread.writeString("211 No extended features\r\n");
+ sessionThread.writeString("211-Features supported\r\n");
+ sessionThread.writeString(" UTF8\r\n"); // advertise UTF8 support (fixes bug 14)
+ sessionThread.writeString("211 End\r\n");
+ myLog.l(Log.DEBUG, "Gave FEAT response");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdLIST.java b/src/main/java/org/swiftp/server/CmdLIST.java
index fe6380559c1adfd3f27958b6816b4c26f482a857..6729f0c1b23639bd5cfc7a6f88bfe3cf9b356016 100644
--- a/src/main/java/org/swiftp/server/CmdLIST.java
+++ b/src/main/java/org/swiftp/server/CmdLIST.java
@@ -1,165 +1,165 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-/* The code that is common to LIST and NLST is implemented in the abstract
- * class CmdAbstractListing, which is inherited here.
- * CmdLIST and CmdNLST just override the
- * makeLsString() function in different ways to provide the different forms
- * of output.
- */
-
-package org.swiftp.server;
-
-import java.io.File;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-import android.util.Log;
-
-public class CmdLIST extends CmdAbstractListing implements Runnable {
- // The approximate number of milliseconds in 6 months
- public final static long MS_IN_SIX_MONTHS = 6 * 30 * 24 * 60 * 60 * 1000;
- private final String input;
-
- public CmdLIST(SessionThread sessionThread, String input) {
- super(sessionThread, input);
- this.input = input;
- }
-
- @Override
- public void run() {
- String errString = null;
-
- mainblock: {
- String param = getParameter(input);
- myLog.d("LIST parameter: " + param);
- while(param.startsWith("-")) {
- // Skip all dashed -args, if present
- myLog.d("LIST is skipping dashed arg " + param);
- param = getParameter(param);
- }
- File fileToList = null;
- if(param.equals("")) {
- fileToList = sessionThread.getWorkingDir();
- } else {
- if(param.contains("*")) {
- errString = "550 LIST does not support wildcards\r\n";
- break mainblock;
- }
- fileToList = new File(sessionThread.getWorkingDir(), param);
- if(violatesChroot(fileToList)) {
- errString = "450 Listing target violates chroot\r\n";
- break mainblock;
- }
- }
- String listing;
- if(fileToList.isDirectory()) {
- StringBuilder response = new StringBuilder();
- errString = listDirectory(response, fileToList);
- if(errString != null) {
- break mainblock;
- }
- listing = response.toString();
- } else {
- listing = makeLsString(fileToList);
- if(listing == null) {
- errString = "450 Couldn't list that file\r\n";
- break mainblock;
- }
- }
- errString = sendListing(listing);
- if(errString != null) {
- break mainblock;
- }
- }
-
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.l(Log.DEBUG, "LIST failed with: " + errString);
- } else {
- myLog.l(Log.DEBUG, "LIST completed OK");
- }
- // The success or error response over the control connection will
- // have already been handled by sendListing, so we can just quit now.
- }
-
- // Generates a line of a directory listing in the traditional /bin/ls
- // format.
- @Override
- protected String makeLsString(File file) {
- StringBuilder response = new StringBuilder();
-
- if(!file.exists()) {
- staticLog.l(Log.INFO, "makeLsString had nonexistent file");
- return null;
- }
-
- // See Daniel Bernstein's explanation of /bin/ls format at:
- // http://cr.yp.to/ftp/list/binls.html
- // This stuff is almost entirely based on his recommendations.
-
- String lastNamePart = file.getName();
- // Many clients can't handle files containing these symbols
- if(lastNamePart.contains("*") ||
- lastNamePart.contains("/"))
- {
- staticLog.l(Log.INFO, "Filename omitted due to disallowed character");
- return null;
- } else {
- // The following line generates many calls in large directories
- //staticLog.l(Log.DEBUG, "Filename: " + lastNamePart);
- }
-
-
- if(file.isDirectory()) {
- response.append("drwxr-xr-x 1 owner group");
- } else {
- // todo: think about special files, symlinks, devices
- response.append("-rw-r--r-- 1 owner group");
- }
-
- // The next field is a 13-byte right-justified space-padded file size
- long fileSize = file.length();
- String sizeString = new Long(fileSize).toString();
- int padSpaces = 13 - sizeString.length();
- while(padSpaces--> 0) {
- response.append(' ');
- }
- response.append(sizeString);
-
- // The format of the timestamp varies depending on whether the mtime
- // is 6 months old
- long mTime = file.lastModified();
- SimpleDateFormat format;
- // Temporarily commented out.. trying to fix Win7 display bug
- if(System.currentTimeMillis() - mTime> MS_IN_SIX_MONTHS) {
- // The mtime is less than 6 months ago
- format = new SimpleDateFormat(" MMM dd HH:mm ", Locale.US);
- } else {
- // The mtime is more than 6 months ago
- format = new SimpleDateFormat(" MMM dd yyyy ", Locale.US);
- }
- response.append(format.format(new Date(file.lastModified())));
- response.append(lastNamePart);
- response.append("\r\n");
- return response.toString();
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+/* The code that is common to LIST and NLST is implemented in the abstract
+ * class CmdAbstractListing, which is inherited here.
+ * CmdLIST and CmdNLST just override the
+ * makeLsString() function in different ways to provide the different forms
+ * of output.
+ */
+
+package org.swiftp.server;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import android.util.Log;
+
+public class CmdLIST extends CmdAbstractListing implements Runnable {
+ // The approximate number of milliseconds in 6 months
+ public final static long MS_IN_SIX_MONTHS = 6 * 30 * 24 * 60 * 60 * 1000;
+ private final String input;
+
+ public CmdLIST(SessionThread sessionThread, String input) {
+ super(sessionThread, input);
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ String errString = null;
+
+ mainblock: {
+ String param = getParameter(input);
+ myLog.d("LIST parameter: " + param);
+ while(param.startsWith("-")) {
+ // Skip all dashed -args, if present
+ myLog.d("LIST is skipping dashed arg " + param);
+ param = getParameter(param);
+ }
+ File fileToList = null;
+ if(param.equals("")) {
+ fileToList = sessionThread.getWorkingDir();
+ } else {
+ if(param.contains("*")) {
+ errString = "550 LIST does not support wildcards\r\n";
+ break mainblock;
+ }
+ fileToList = new File(sessionThread.getWorkingDir(), param);
+ if(violatesChroot(fileToList)) {
+ errString = "450 Listing target violates chroot\r\n";
+ break mainblock;
+ }
+ }
+ String listing;
+ if(fileToList.isDirectory()) {
+ StringBuilder response = new StringBuilder();
+ errString = listDirectory(response, fileToList);
+ if(errString != null) {
+ break mainblock;
+ }
+ listing = response.toString();
+ } else {
+ listing = makeLsString(fileToList);
+ if(listing == null) {
+ errString = "450 Couldn't list that file\r\n";
+ break mainblock;
+ }
+ }
+ errString = sendListing(listing);
+ if(errString != null) {
+ break mainblock;
+ }
+ }
+
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.l(Log.DEBUG, "LIST failed with: " + errString);
+ } else {
+ myLog.l(Log.DEBUG, "LIST completed OK");
+ }
+ // The success or error response over the control connection will
+ // have already been handled by sendListing, so we can just quit now.
+ }
+
+ // Generates a line of a directory listing in the traditional /bin/ls
+ // format.
+ @Override
+ protected String makeLsString(File file) {
+ StringBuilder response = new StringBuilder();
+
+ if(!file.exists()) {
+ staticLog.l(Log.INFO, "makeLsString had nonexistent file");
+ return null;
+ }
+
+ // See Daniel Bernstein's explanation of /bin/ls format at:
+ // http://cr.yp.to/ftp/list/binls.html
+ // This stuff is almost entirely based on his recommendations.
+
+ String lastNamePart = file.getName();
+ // Many clients can't handle files containing these symbols
+ if(lastNamePart.contains("*") ||
+ lastNamePart.contains("/"))
+ {
+ staticLog.l(Log.INFO, "Filename omitted due to disallowed character");
+ return null;
+ } else {
+ // The following line generates many calls in large directories
+ //staticLog.l(Log.DEBUG, "Filename: " + lastNamePart);
+ }
+
+
+ if(file.isDirectory()) {
+ response.append("drwxr-xr-x 1 owner group");
+ } else {
+ // todo: think about special files, symlinks, devices
+ response.append("-rw-r--r-- 1 owner group");
+ }
+
+ // The next field is a 13-byte right-justified space-padded file size
+ long fileSize = file.length();
+ String sizeString = new Long(fileSize).toString();
+ int padSpaces = 13 - sizeString.length();
+ while(padSpaces--> 0) {
+ response.append(' ');
+ }
+ response.append(sizeString);
+
+ // The format of the timestamp varies depending on whether the mtime
+ // is 6 months old
+ long mTime = file.lastModified();
+ SimpleDateFormat format;
+ // Temporarily commented out.. trying to fix Win7 display bug
+ if(System.currentTimeMillis() - mTime> MS_IN_SIX_MONTHS) {
+ // The mtime is less than 6 months ago
+ format = new SimpleDateFormat(" MMM dd HH:mm ", Locale.US);
+ } else {
+ // The mtime is more than 6 months ago
+ format = new SimpleDateFormat(" MMM dd yyyy ", Locale.US);
+ }
+ response.append(format.format(new Date(file.lastModified())));
+ response.append(lastNamePart);
+ response.append("\r\n");
+ return response.toString();
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdMKD.java b/src/main/java/org/swiftp/server/CmdMKD.java
index 0b88776c5e222502f68bb6892f6edeeb8a3f0c30..893765b618b11045d70bfffccafd0dfd3d4bde6a 100644
--- a/src/main/java/org/swiftp/server/CmdMKD.java
+++ b/src/main/java/org/swiftp/server/CmdMKD.java
@@ -1,70 +1,76 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import android.util.Log;
-
-public class CmdMKD extends FtpCmd implements Runnable {
- String input;
-
- public CmdMKD(SessionThread sessionThread, String input) {
- super(sessionThread, CmdMKD.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "MKD executing");
- String param = getParameter(input);
- File toCreate;
- String errString = null;
- mainblock: {
- // If the param is an absolute path, use it as is. If it's a
- // relative path, prepend the current working directory.
- if(param.length() < 1) { - errString = "550 Invalid name\r\n"; - break mainblock; - } - toCreate = inputPathToChrootedFile(sessionThread.getWorkingDir(), param); - if(violatesChroot(toCreate)) { - errString = "550 Invalid name or chroot violation\r\n"; - break mainblock; - } - if(toCreate.exists()) { - errString = "550 Already exists\r\n"; - break mainblock; - } - if(!toCreate.mkdir()) { - errString = "550 Error making directory (permissions?)\r\n"; - break mainblock; - } - } - if(errString != null) { - sessionThread.writeString(errString); - myLog.l(Log.INFO, "MKD error: " + errString.trim()); - } else { - sessionThread.writeString("250 Directory created\r\n"); - } - myLog.l(Log.INFO, "MKD complete"); - } - -} +/* +Copyright 2009 David Revell + +This file is part of SwiFTP. + +SwiFTP is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +SwiFTP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import android.support.v4.provider.DocumentFile;
+import android.util.Log;
+
+import org.swiftp.Globals;
+
+import util.DocumentUtil;
+import util.FileUtil;
+
+public class CmdMKD extends FtpCmd implements Runnable {
+ String input;
+
+ public CmdMKD(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdMKD.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "MKD executing");
+ String param = getParameter(input);
+ File toCreate;
+ String errString = null;
+ mainblock: {
+ // If the param is an absolute path, use it as is. If it's a
+ // relative path, prepend the current working directory.
+ if(param.length() < 1) { + errString = "550 Invalid name\r\n"; + break mainblock; + } + toCreate = inputPathToChrootedFile(sessionThread.getWorkingDir(), param); + if(violatesChroot(toCreate)) { + errString = "550 Invalid name or chroot violation\r\n"; + break mainblock; + } + if(toCreate.exists()) { + errString = "550 Already exists\r\n"; + break mainblock; + } + if(!FileUtil.mkdir(toCreate)) { + errString = "550 Error making directory (permissions?)\r\n"; + break mainblock; + } + } + if(errString != null) { + sessionThread.writeString(errString); + myLog.l(Log.INFO, "MKD error: " + errString.trim()); + } else { + sessionThread.writeString("250 Directory created\r\n"); + } + myLog.l(Log.INFO, "MKD complete"); + } + +} diff --git a/src/main/java/org/swiftp/server/CmdMap.java b/src/main/java/org/swiftp/server/CmdMap.java index 628c91c6b60584263cf9133b71872a1a262a4db6..b9c657a57c8890d8614a71ea2f5da070af2b0747 100644 --- a/src/main/java/org/swiftp/server/CmdMap.java +++ b/src/main/java/org/swiftp/server/CmdMap.java @@ -1,48 +1,48 @@ -/* -Copyright 2009 David Revell - -This file is part of SwiFTP. - -SwiFTP is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -SwiFTP is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-public class CmdMap {
- protected Class extends FtpCmd> cmdClass;
- String name;
-
-
- public CmdMap(String name, Class extends FtpCmd> cmdClass) {
- super();
- this.name = name;
- this.cmdClass = cmdClass;
- }
-
- public Class extends FtpCmd> getCommand() {
- return cmdClass;
- }
-
- public void setCommand(Class extends FtpCmd> cmdClass) {
- this.cmdClass = cmdClass;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(String name) {
- this.name = name;
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+public class CmdMap {
+ protected Class extends FtpCmd> cmdClass;
+ String name;
+
+
+ public CmdMap(String name, Class extends FtpCmd> cmdClass) {
+ super();
+ this.name = name;
+ this.cmdClass = cmdClass;
+ }
+
+ public Class extends FtpCmd> getCommand() {
+ return cmdClass;
+ }
+
+ public void setCommand(Class extends FtpCmd> cmdClass) {
+ this.cmdClass = cmdClass;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdNLST.java b/src/main/java/org/swiftp/server/CmdNLST.java
index 87ec292826d6b5e7edecba7a01a043f857521f9c..fd84a78a0168d3d1ff94dd13a3172b33367e5e3e 100644
--- a/src/main/java/org/swiftp/server/CmdNLST.java
+++ b/src/main/java/org/swiftp/server/CmdNLST.java
@@ -1,128 +1,128 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-/* The code that is common to LIST and NLST is implemented in the abstract
- * class CmdAbstractListing, which is inherited here.
- * CmdLIST and CmdNLST just override the
- * makeLsString() function in different ways to provide the different forms
- * of output.
- */
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import android.util.Log;
-
-public class CmdNLST extends CmdAbstractListing implements Runnable {
- // The approximate number of milliseconds in 6 months
- public final static long MS_IN_SIX_MONTHS = 6 * 30 * 24 * 60 * 60 * 1000;
- private final String input;
-
-
- public CmdNLST(SessionThread sessionThread, String input) {
- super(sessionThread, input);
- this.input = input;
- }
-
- @Override
- public void run() {
- String errString = null;
-
- mainblock: {
- String param = getParameter(input);
- if(param.startsWith("-")) {
- // Ignore options to list, which start with a dash
- param = "";
- }
- File fileToList = null;
- if(param.equals("")) {
- fileToList = sessionThread.getWorkingDir();
- } else {
- if(param.contains("*")) {
- errString = "550 NLST does not support wildcards\r\n";
- break mainblock;
- }
- fileToList = new File(sessionThread.getWorkingDir(), param);
- if(violatesChroot(fileToList)) {
- errString = "450 Listing target violates chroot\r\n";
- break mainblock;
- } else if(fileToList.isFile()) {
- // Bernstein suggests that NLST should fail when a
- // parameter is given and the parameter names a regular
- // file (not a directory).
- errString = "550 NLST for regular files is unsupported\r\n";
- break mainblock;
- }
- }
- String listing;
- if(fileToList.isDirectory()) {
- StringBuilder response = new StringBuilder();
- errString = listDirectory(response, fileToList);
- if(errString != null) {
- break mainblock;
- }
- listing = response.toString();
- } else {
- listing = makeLsString(fileToList);
- if(listing == null) {
- errString = "450 Couldn't list that file\r\n";
- break mainblock;
- }
- }
- errString = sendListing(listing);
- if(errString != null) {
- break mainblock;
- }
- }
-
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.l(Log.DEBUG, "NLST failed with: " + errString);
- } else {
- myLog.l(Log.DEBUG, "NLST completed OK");
- }
- // The success or error response over the control connection will
- // have already been handled by sendListing, so we can just quit now.
- }
-
- @Override
- protected String makeLsString(File file) {
- if(!file.exists()) {
- staticLog.l(Log.INFO, "makeLsString had nonexistent file");
- return null;
- }
-
- // See Daniel Bernstein's explanation of NLST format at:
- // http://cr.yp.to/ftp/list/binls.html
- // This stuff is almost entirely based on his recommendations.
-
- String lastNamePart = file.getName();
- // Many clients can't handle files containing these symbols
- if(lastNamePart.contains("*") ||
- lastNamePart.contains("/"))
- {
- staticLog.l(Log.INFO, "Filename omitted due to disallowed character");
- return null;
- } else {
- staticLog.l(Log.DEBUG, "Filename: " + lastNamePart );
- return lastNamePart + "\r\n";
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+/* The code that is common to LIST and NLST is implemented in the abstract
+ * class CmdAbstractListing, which is inherited here.
+ * CmdLIST and CmdNLST just override the
+ * makeLsString() function in different ways to provide the different forms
+ * of output.
+ */
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import android.util.Log;
+
+public class CmdNLST extends CmdAbstractListing implements Runnable {
+ // The approximate number of milliseconds in 6 months
+ public final static long MS_IN_SIX_MONTHS = 6 * 30 * 24 * 60 * 60 * 1000;
+ private final String input;
+
+
+ public CmdNLST(SessionThread sessionThread, String input) {
+ super(sessionThread, input);
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ String errString = null;
+
+ mainblock: {
+ String param = getParameter(input);
+ if(param.startsWith("-")) {
+ // Ignore options to list, which start with a dash
+ param = "";
+ }
+ File fileToList = null;
+ if(param.equals("")) {
+ fileToList = sessionThread.getWorkingDir();
+ } else {
+ if(param.contains("*")) {
+ errString = "550 NLST does not support wildcards\r\n";
+ break mainblock;
+ }
+ fileToList = new File(sessionThread.getWorkingDir(), param);
+ if(violatesChroot(fileToList)) {
+ errString = "450 Listing target violates chroot\r\n";
+ break mainblock;
+ } else if(fileToList.isFile()) {
+ // Bernstein suggests that NLST should fail when a
+ // parameter is given and the parameter names a regular
+ // file (not a directory).
+ errString = "550 NLST for regular files is unsupported\r\n";
+ break mainblock;
+ }
+ }
+ String listing;
+ if(fileToList.isDirectory()) {
+ StringBuilder response = new StringBuilder();
+ errString = listDirectory(response, fileToList);
+ if(errString != null) {
+ break mainblock;
+ }
+ listing = response.toString();
+ } else {
+ listing = makeLsString(fileToList);
+ if(listing == null) {
+ errString = "450 Couldn't list that file\r\n";
+ break mainblock;
+ }
+ }
+ errString = sendListing(listing);
+ if(errString != null) {
+ break mainblock;
+ }
+ }
+
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.l(Log.DEBUG, "NLST failed with: " + errString);
+ } else {
+ myLog.l(Log.DEBUG, "NLST completed OK");
+ }
+ // The success or error response over the control connection will
+ // have already been handled by sendListing, so we can just quit now.
+ }
+
+ @Override
+ protected String makeLsString(File file) {
+ if(!file.exists()) {
+ staticLog.l(Log.INFO, "makeLsString had nonexistent file");
+ return null;
+ }
+
+ // See Daniel Bernstein's explanation of NLST format at:
+ // http://cr.yp.to/ftp/list/binls.html
+ // This stuff is almost entirely based on his recommendations.
+
+ String lastNamePart = file.getName();
+ // Many clients can't handle files containing these symbols
+ if(lastNamePart.contains("*") ||
+ lastNamePart.contains("/"))
+ {
+ staticLog.l(Log.INFO, "Filename omitted due to disallowed character");
+ return null;
+ } else {
+ staticLog.l(Log.DEBUG, "Filename: " + lastNamePart );
+ return lastNamePart + "\r\n";
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdNOOP.java b/src/main/java/org/swiftp/server/CmdNOOP.java
index 422ab2bf80236a1654197680b2afaab00c8fe918..224cc0cced4d39f49a0517172a3a4fd54c366178 100644
--- a/src/main/java/org/swiftp/server/CmdNOOP.java
+++ b/src/main/java/org/swiftp/server/CmdNOOP.java
@@ -1,36 +1,36 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-
-public class CmdNOOP extends FtpCmd implements Runnable {
- public static final String message = "TEMPLATE!!";
-
- public CmdNOOP(SessionThread sessionThread, String input) {
- super(sessionThread, CmdNOOP.class.toString());
- }
-
- @Override
- public void run() {
- sessionThread.writeString("200 NOOP ok\r\n");
- //myLog.l(Log.INFO, "Executing NOOP, done");
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+
+public class CmdNOOP extends FtpCmd implements Runnable {
+ public static final String message = "TEMPLATE!!";
+
+ public CmdNOOP(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdNOOP.class.toString());
+ }
+
+ @Override
+ public void run() {
+ sessionThread.writeString("200 NOOP ok\r\n");
+ //myLog.l(Log.INFO, "Executing NOOP, done");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdOPTS.java b/src/main/java/org/swiftp/server/CmdOPTS.java
index 5b0ba20682666bfca86bb163914a8b1f0ef00650..b76644fe51b13ff2fedac5e5bde28116a9da8744 100644
--- a/src/main/java/org/swiftp/server/CmdOPTS.java
+++ b/src/main/java/org/swiftp/server/CmdOPTS.java
@@ -1,76 +1,76 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-
-public class CmdOPTS extends FtpCmd implements Runnable {
- public static final String message = "TEMPLATE!!";
- private final String input;
-
- public CmdOPTS(SessionThread sessionThread, String input) {
- super(sessionThread, CmdOPTS.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- String param = getParameter(input);
- String errString = null;
-
- mainBlock: {
- if(param == null) {
- errString = "550 Need argument to OPTS\r\n";
- myLog.w("Couldn't understand empty OPTS command");
- break mainBlock;
- }
- String[] splits = param.split(" ");
- if(splits.length != 2) {
- errString = "550 Malformed OPTS command\r\n";
- myLog.w("Couldn't parse OPTS command");
- break mainBlock;
- }
- String optName = splits[0].toUpperCase();
- String optVal = splits[1].toUpperCase();
- if(optName.equals("UTF8")) {
- // OK, whatever. Don't really know what to do here. We
- // always operate in UTF8 mode.
- if(optVal.equals("ON")) {
- myLog.d("Got OPTS UTF8 ON");
- sessionThread.setEncoding("UTF-8");
- } else {
- myLog.i("Ignoring OPTS UTF8 for something besides ON");
- }
- break mainBlock;
- } else {
- myLog.d("Unrecognized OPTS option: " + optName);
- errString = "502 Unrecognized option\r\n";
- break mainBlock;
- }
- }
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.i("Template log message");
- } else {
- sessionThread.writeString("200 OPTS accepted\r\n");
- myLog.d("Handled OPTS ok");
- }
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+
+public class CmdOPTS extends FtpCmd implements Runnable {
+ public static final String message = "TEMPLATE!!";
+ private final String input;
+
+ public CmdOPTS(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdOPTS.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ String param = getParameter(input);
+ String errString = null;
+
+ mainBlock: {
+ if(param == null) {
+ errString = "550 Need argument to OPTS\r\n";
+ myLog.w("Couldn't understand empty OPTS command");
+ break mainBlock;
+ }
+ String[] splits = param.split(" ");
+ if(splits.length != 2) {
+ errString = "550 Malformed OPTS command\r\n";
+ myLog.w("Couldn't parse OPTS command");
+ break mainBlock;
+ }
+ String optName = splits[0].toUpperCase();
+ String optVal = splits[1].toUpperCase();
+ if(optName.equals("UTF8")) {
+ // OK, whatever. Don't really know what to do here. We
+ // always operate in UTF8 mode.
+ if(optVal.equals("ON")) {
+ myLog.d("Got OPTS UTF8 ON");
+ sessionThread.setEncoding("UTF-8");
+ } else {
+ myLog.i("Ignoring OPTS UTF8 for something besides ON");
+ }
+ break mainBlock;
+ } else {
+ myLog.d("Unrecognized OPTS option: " + optName);
+ errString = "502 Unrecognized option\r\n";
+ break mainBlock;
+ }
+ }
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.i("Template log message");
+ } else {
+ sessionThread.writeString("200 OPTS accepted\r\n");
+ myLog.d("Handled OPTS ok");
+ }
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdPASS.java b/src/main/java/org/swiftp/server/CmdPASS.java
index df205751a29fb611b00473772e25a62b6f453a4f..67d30755e780aee33f07c3de36aa0bab22c79210 100644
--- a/src/main/java/org/swiftp/server/CmdPASS.java
+++ b/src/main/java/org/swiftp/server/CmdPASS.java
@@ -1,96 +1,96 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import org.swiftp.Globals;
-import org.swiftp.R;
-import org.swiftp.Util;
-
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-public class CmdPASS extends FtpCmd implements Runnable {
- String input;
-
- public CmdPASS(SessionThread sessionThread, String input) {
- // We can just discard the password for now. We're just
- // following the expected dialogue, we're going to allow
- // access in any case.
- super(sessionThread, CmdPASS.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- // User must have already executed a USER command to
- // populate the Account object's username
- myLog.l(Log.DEBUG, "Executing PASS");
-
- String attemptPassword = getParameter(input, true); // silent
- String attemptUsername = sessionThread.account.getUsername();
- if(attemptUsername == null) {
- sessionThread.writeString("503 Must send USER first\r\n");
- return;
- }
- Context ctx = Globals.getContext();
- if(ctx == null) {
- // This will probably never happen, since the global
- // context is configured by the Service
- myLog.l(Log.ERROR, "No global context in PASS\r\n");
- }
- String password;
- String username;
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(ctx);
- username = settings.getString(ctx.getString(R.string.key_username), "");
- password = settings.getString(ctx.getString(R.string.key_ftp_pwd), "");
-// username = Util.getSP(ctx, "ftp.username");
-// password = Util.getSP(ctx, "ftp.pwd");
-
- if (username.equals("")) {
- username = Util.getCode(ctx);
- }
- if (password.equals("")) {
- password = Util.getCode(ctx);
- }
- //username = settings.getString("username", null);
- //password = settings.getString("password", null);
- if(username == null || password == null) {
- myLog.l(Log.ERROR, "Username or password misconfigured");
- sessionThread.writeString("500 Internal error during authentication");
- } else if(username.equals(attemptUsername) &&
- password.equals(attemptPassword)) {
- sessionThread.writeString("230 Access granted\r\n");
- myLog.l(Log.INFO, "User " + username + " password verified");
- sessionThread.authAttempt(true);
- } else {
- try {
- // If the login failed, sleep for one second to foil
- // brute force attacks
- Thread.sleep(1000);
- } catch(InterruptedException e) {}
- myLog.l(Log.INFO, "Failed authentication");
- sessionThread.writeString("530 Login incorrect.\r\n");
- sessionThread.authAttempt(false);
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import org.swiftp.Globals;
+import org.swiftp.R;
+import org.swiftp.Util;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+public class CmdPASS extends FtpCmd implements Runnable {
+ String input;
+
+ public CmdPASS(SessionThread sessionThread, String input) {
+ // We can just discard the password for now. We're just
+ // following the expected dialogue, we're going to allow
+ // access in any case.
+ super(sessionThread, CmdPASS.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ // User must have already executed a USER command to
+ // populate the Account object's username
+ myLog.l(Log.DEBUG, "Executing PASS");
+
+ String attemptPassword = getParameter(input, true); // silent
+ String attemptUsername = sessionThread.account.getUsername();
+ if(attemptUsername == null) {
+ sessionThread.writeString("503 Must send USER first\r\n");
+ return;
+ }
+ Context ctx = Globals.getContext();
+ if(ctx == null) {
+ // This will probably never happen, since the global
+ // context is configured by the Service
+ myLog.l(Log.ERROR, "No global context in PASS\r\n");
+ }
+ String password;
+ String username;
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(ctx);
+ username = settings.getString(ctx.getString(R.string.key_username), "");
+ password = settings.getString(ctx.getString(R.string.key_ftp_pwd), "");
+// username = Util.getSP(ctx, "ftp.username");
+// password = Util.getSP(ctx, "ftp.pwd");
+
+ if (username.equals("")) {
+ username = Util.getCode(ctx);
+ }
+ if (password.equals("")) {
+ password = Util.getCode(ctx);
+ }
+ //username = settings.getString("username", null);
+ //password = settings.getString("password", null);
+ if(username == null || password == null) {
+ myLog.l(Log.ERROR, "Username or password misconfigured");
+ sessionThread.writeString("500 Internal error during authentication");
+ } else if(username.equals(attemptUsername) &&
+ password.equals(attemptPassword)) {
+ sessionThread.writeString("230 Access granted\r\n");
+ myLog.l(Log.INFO, "User " + username + " password verified");
+ sessionThread.authAttempt(true);
+ } else {
+ try {
+ // If the login failed, sleep for one second to foil
+ // brute force attacks
+ Thread.sleep(1000);
+ } catch(InterruptedException e) {}
+ myLog.l(Log.INFO, "Failed authentication");
+ sessionThread.writeString("530 Login incorrect.\r\n");
+ sessionThread.authAttempt(false);
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdPASV.java b/src/main/java/org/swiftp/server/CmdPASV.java
index 83754fcba4b1b460c1203a1eb0358daca64c7eb7..e1da05a6c534dafec6121ebf6283e5a5d362fed4 100644
--- a/src/main/java/org/swiftp/server/CmdPASV.java
+++ b/src/main/java/org/swiftp/server/CmdPASV.java
@@ -1,72 +1,72 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.net.InetAddress;
-
-import android.util.Log;
-
-public class CmdPASV extends FtpCmd implements Runnable {
- //public static final String message = "TEMPLATE!!";
-
- public CmdPASV(SessionThread sessionThread, String input) {
- super(sessionThread, CmdPASV.class.toString());
- }
-
- @Override
- public void run() {
- String cantOpen = "502 Couldn't open a port\r\n";
- myLog.l(Log.DEBUG, "PASV running");
- int port;
- if((port = sessionThread.onPasv()) == 0) {
- // There was a problem opening a port
- myLog.l(Log.ERROR, "Couldn't open a port for PASV");
- sessionThread.writeString(cantOpen);
- return;
- }
- InetAddress addr = sessionThread.getDataSocketPasvIp();
-
- if(addr == null) {
- myLog.l(Log.ERROR, "PASV IP string invalid");
- sessionThread.writeString(cantOpen);
- return;
- }
- myLog.d("PASV sending IP: " + addr.getHostAddress());
- if(port < 1) { - myLog.l(Log.ERROR, "PASV port number invalid"); - sessionThread.writeString(cantOpen); - return; - } - StringBuilder response = new StringBuilder( - "227 Entering Passive Mode ("); - // Output our IP address in the format xxx,xxx,xxx,xxx - response.append(addr.getHostAddress().replace('.', ',')); - response.append(","); - - // Output our port in the format p1,p2 where port=p1*256+p2 - response.append(port / 256); - response.append(","); - response.append(port % 256); - response.append(").\r\n"); - String responseString = response.toString(); - sessionThread.writeString(responseString); - myLog.l(Log.DEBUG, "PASV completed, sent: " + responseString); - } -} +/* +Copyright 2009 David Revell + +This file is part of SwiFTP. + +SwiFTP is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +SwiFTP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.net.InetAddress;
+
+import android.util.Log;
+
+public class CmdPASV extends FtpCmd implements Runnable {
+ //public static final String message = "TEMPLATE!!";
+
+ public CmdPASV(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdPASV.class.toString());
+ }
+
+ @Override
+ public void run() {
+ String cantOpen = "502 Couldn't open a port\r\n";
+ myLog.l(Log.DEBUG, "PASV running");
+ int port;
+ if((port = sessionThread.onPasv()) == 0) {
+ // There was a problem opening a port
+ myLog.l(Log.ERROR, "Couldn't open a port for PASV");
+ sessionThread.writeString(cantOpen);
+ return;
+ }
+ InetAddress addr = sessionThread.getDataSocketPasvIp();
+
+ if(addr == null) {
+ myLog.l(Log.ERROR, "PASV IP string invalid");
+ sessionThread.writeString(cantOpen);
+ return;
+ }
+ myLog.d("PASV sending IP: " + addr.getHostAddress());
+ if(port < 1) { + myLog.l(Log.ERROR, "PASV port number invalid"); + sessionThread.writeString(cantOpen); + return; + } + StringBuilder response = new StringBuilder( + "227 Entering Passive Mode ("); + // Output our IP address in the format xxx,xxx,xxx,xxx + response.append(addr.getHostAddress().replace('.', ',')); + response.append(","); + + // Output our port in the format p1,p2 where port=p1*256+p2 + response.append(port / 256); + response.append(","); + response.append(port % 256); + response.append(").\r\n"); + String responseString = response.toString(); + sessionThread.writeString(responseString); + myLog.l(Log.DEBUG, "PASV completed, sent: " + responseString); + } +} diff --git a/src/main/java/org/swiftp/server/CmdPORT.java b/src/main/java/org/swiftp/server/CmdPORT.java index a78874bf45272c056fea81945acd324a6292a388..6eff5ee51b8559d14777b59d9cc27e62b9f834ba 100644 --- a/src/main/java/org/swiftp/server/CmdPORT.java +++ b/src/main/java/org/swiftp/server/CmdPORT.java @@ -1,98 +1,98 @@ -/* -Copyright 2009 David Revell - -This file is part of SwiFTP. - -SwiFTP is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -SwiFTP is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-import android.util.Log;
-
-public class CmdPORT extends FtpCmd implements Runnable {
- //public static final String message = "TEMPLATE!!";
- String input;
-
- public CmdPORT(SessionThread sessionThread, String input) {
- super(sessionThread, CmdPORT.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "PORT running");
- String errString = null;
- mainBlock: {
- String param = getParameter(input);
- if(param.contains("|") && param.contains("::")) {
- errString = "550 No IPv6 support, reconfigure your client\r\n";
- break mainBlock;
- }
- String[] substrs = param.split(",");
- if(substrs.length != 6) {
- errString = "550 Malformed PORT argument\r\n";
- break mainBlock;
- }
- for(int i=0; i 3)
- {
- errString = "550 Invalid PORT argument: " + substrs[i] +
- "\r\n";
- break mainBlock;
- }
- }
- byte[] ipBytes = new byte[4];
- for(int i=0; i<4; i++) { - try { - // We have to manually convert unsigned to signed - // byte representation. - int ipByteAsInt = Integer.parseInt(substrs[i]); - if(ipByteAsInt>= 128) {
- ipByteAsInt -= 256;
- }
- ipBytes[i] = (byte)ipByteAsInt;
- } catch (Exception e) {
- errString = "550 Invalid PORT format: "
- + substrs[i] + "\r\n";
- break mainBlock;
- }
- }
- InetAddress inetAddr;
- try {
- inetAddr = InetAddress.getByAddress(ipBytes);
- } catch (UnknownHostException e) {
- errString = "550 Unknown host\r\n";
- break mainBlock;
- }
-
- int port = Integer.parseInt(substrs[4]) * 256 +
- Integer.parseInt(substrs[5]);
-
- sessionThread.onPort(inetAddr, port);
- }
- if(errString == null) {
- sessionThread.writeString("200 PORT OK\r\n");
- myLog.l(Log.DEBUG, "PORT completed");
- } else {
- myLog.l(Log.INFO, "PORT error: " + errString);
- sessionThread.writeString(errString);
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import android.util.Log;
+
+public class CmdPORT extends FtpCmd implements Runnable {
+ //public static final String message = "TEMPLATE!!";
+ String input;
+
+ public CmdPORT(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdPORT.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "PORT running");
+ String errString = null;
+ mainBlock: {
+ String param = getParameter(input);
+ if(param.contains("|") && param.contains("::")) {
+ errString = "550 No IPv6 support, reconfigure your client\r\n";
+ break mainBlock;
+ }
+ String[] substrs = param.split(",");
+ if(substrs.length != 6) {
+ errString = "550 Malformed PORT argument\r\n";
+ break mainBlock;
+ }
+ for(int i=0; i 3)
+ {
+ errString = "550 Invalid PORT argument: " + substrs[i] +
+ "\r\n";
+ break mainBlock;
+ }
+ }
+ byte[] ipBytes = new byte[4];
+ for(int i=0; i<4; i++) { + try { + // We have to manually convert unsigned to signed + // byte representation. + int ipByteAsInt = Integer.parseInt(substrs[i]); + if(ipByteAsInt>= 128) {
+ ipByteAsInt -= 256;
+ }
+ ipBytes[i] = (byte)ipByteAsInt;
+ } catch (Exception e) {
+ errString = "550 Invalid PORT format: "
+ + substrs[i] + "\r\n";
+ break mainBlock;
+ }
+ }
+ InetAddress inetAddr;
+ try {
+ inetAddr = InetAddress.getByAddress(ipBytes);
+ } catch (UnknownHostException e) {
+ errString = "550 Unknown host\r\n";
+ break mainBlock;
+ }
+
+ int port = Integer.parseInt(substrs[4]) * 256 +
+ Integer.parseInt(substrs[5]);
+
+ sessionThread.onPort(inetAddr, port);
+ }
+ if(errString == null) {
+ sessionThread.writeString("200 PORT OK\r\n");
+ myLog.l(Log.DEBUG, "PORT completed");
+ } else {
+ myLog.l(Log.INFO, "PORT error: " + errString);
+ sessionThread.writeString(errString);
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdPWD.java b/src/main/java/org/swiftp/server/CmdPWD.java
index 39e61204d32a410fbfbeba270e9afe896b1c7e74..5ae93711fa9274b9bdc67e60e263133d8754d0a5 100644
--- a/src/main/java/org/swiftp/server/CmdPWD.java
+++ b/src/main/java/org/swiftp/server/CmdPWD.java
@@ -1,64 +1,64 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.IOException;
-
-import org.swiftp.Globals;
-
-import android.util.Log;
-
-public class CmdPWD extends FtpCmd implements Runnable {
-// public static final String message = "TEMPLATE!!";
-
- public CmdPWD(SessionThread sessionThread, String input) {
- super(sessionThread, CmdPWD.class.toString());
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "PWD executing");
-
- // We assume that the chroot restriction has been applied, and that
- // therefore the current directory is located somewhere within the
- // chroot directory. Therefore, we can just slice of the chroot
- // part of the current directory path in order to get the
- // user-visible path (inside the chroot directory).
- try {
- String currentDir = sessionThread.getWorkingDir().getCanonicalPath();
- currentDir = currentDir.substring(Globals.getChrootDir().
- getCanonicalPath().length());
- // The root directory requires special handling to restore its
- // leading slash
- if(currentDir.length() == 0) {
- currentDir = "/";
- }
- sessionThread.writeString("257 \""
- + currentDir
- + "\"\r\n");
- } catch (IOException e) {
- // This shouldn't happen unless our input validation has failed
- myLog.l(Log.ERROR, "PWD canonicalize");
- sessionThread.closeSocket(); // should cause thread termination
- }
- myLog.l(Log.DEBUG, "PWD complete");
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.IOException;
+
+import org.swiftp.Globals;
+
+import android.util.Log;
+
+public class CmdPWD extends FtpCmd implements Runnable {
+// public static final String message = "TEMPLATE!!";
+
+ public CmdPWD(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdPWD.class.toString());
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "PWD executing");
+
+ // We assume that the chroot restriction has been applied, and that
+ // therefore the current directory is located somewhere within the
+ // chroot directory. Therefore, we can just slice of the chroot
+ // part of the current directory path in order to get the
+ // user-visible path (inside the chroot directory).
+ try {
+ String currentDir = sessionThread.getWorkingDir().getCanonicalPath();
+ currentDir = currentDir.substring(Globals.getChrootDir().
+ getCanonicalPath().length());
+ // The root directory requires special handling to restore its
+ // leading slash
+ if(currentDir.length() == 0) {
+ currentDir = "/";
+ }
+ sessionThread.writeString("257 \""
+ + currentDir
+ + "\"\r\n");
+ } catch (IOException e) {
+ // This shouldn't happen unless our input validation has failed
+ myLog.l(Log.ERROR, "PWD canonicalize");
+ sessionThread.closeSocket(); // should cause thread termination
+ }
+ myLog.l(Log.DEBUG, "PWD complete");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdQUIT.java b/src/main/java/org/swiftp/server/CmdQUIT.java
index af9896b588180dc1ab606ce367d2cb95b201b0b5..23412d3f77974c2731f183a9050006009764dfdf 100644
--- a/src/main/java/org/swiftp/server/CmdQUIT.java
+++ b/src/main/java/org/swiftp/server/CmdQUIT.java
@@ -1,38 +1,38 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import android.util.Log;
-
-public class CmdQUIT extends FtpCmd implements Runnable {
- public static final String message = "TEMPLATE!!";
-
- public CmdQUIT(SessionThread sessionThread, String input) {
- super(sessionThread, CmdQUIT.class.toString());
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "QUITting");
- sessionThread.writeString("221 Goodbye\r\n");
- sessionThread.closeSocket();
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import android.util.Log;
+
+public class CmdQUIT extends FtpCmd implements Runnable {
+ public static final String message = "TEMPLATE!!";
+
+ public CmdQUIT(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdQUIT.class.toString());
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "QUITting");
+ sessionThread.writeString("221 Goodbye\r\n");
+ sessionThread.closeSocket();
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdRETR.java b/src/main/java/org/swiftp/server/CmdRETR.java
index 508dc0255b974bdb898d2904e94600464d8bf7d5..31bde621dc6271d64bf2d05c2ab42c78a316400f 100644
--- a/src/main/java/org/swiftp/server/CmdRETR.java
+++ b/src/main/java/org/swiftp/server/CmdRETR.java
@@ -1,158 +1,158 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-import org.swiftp.Defaults;
-
-import android.util.Log;
-
-public class CmdRETR extends FtpCmd implements Runnable {
- //public static final String message = "TEMPLATE!!";
- protected String input;
-
- public CmdRETR(SessionThread sessionThread, String input) {
- super(sessionThread, CmdRETR.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "RETR executing");
- String param = getParameter(input);
- File fileToRetr;
- String errString = null;
-
- mainblock: {
- fileToRetr = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
- if(violatesChroot(fileToRetr)) {
- errString = "550 Invalid name or chroot violation\r\n";
- break mainblock;
- } else if(fileToRetr.isDirectory()) {
- myLog.l(Log.DEBUG, "Ignoring RETR for directory");
- errString = "550 Can't RETR a directory\r\n";
- break mainblock;
- } else if(!fileToRetr.exists()) {
- myLog.l(Log.INFO, "Can't RETR nonexistent file: " +
- fileToRetr.getAbsolutePath());
- errString = "550 File does not exist\r\n";
- break mainblock;
- } else if(!fileToRetr.canRead()) {
- myLog.l(Log.INFO, "Failed RETR permission (canRead() is false)");
- errString = "550 No read permissions\r\n";
- break mainblock;
- } /*else if(!sessionThread.isBinaryMode()) {
- myLog.l(Log.INFO, "Failed RETR in text mode");
- errString = "550 Text mode RETR not supported\r\n";
- break mainblock;
- }*/
- FileInputStream in = null;
- try {
- in = new FileInputStream(fileToRetr);
- byte[] buffer = new byte[Defaults.getDataChunkSize()];
- int bytesRead;
- if(sessionThread.startUsingDataSocket()) {
- myLog.l(Log.DEBUG, "RETR opened data socket");
- } else {
- errString = "425 Error opening socket\r\n";
- myLog.l(Log.INFO, "Error in initDataSocket()");
- break mainblock;
- }
- sessionThread.writeString("150 Sending file\r\n");
- if(sessionThread.isBinaryMode()) {
- myLog.l(Log.DEBUG, "Transferring in binary mode");
- while((bytesRead = in.read(buffer)) != -1) {
- //myLog.l(Log.DEBUG,
- // String.format("CmdRETR sending %d bytes", bytesRead));
- if(sessionThread
- .sendViaDataSocket(buffer, bytesRead) == false)
- {
- errString = "426 Data socket error\r\n";
- myLog.l(Log.INFO, "Data socket error");
- break mainblock;
- }
- }
- } else { // We're in ASCII mode
- myLog.l(Log.DEBUG, "Transferring in ASCII mode");
- // We have to convert all solitary \n to \r\n
- boolean lastBufEndedWithCR = false;
- while((bytesRead = in.read(buffer)) != -1) {
- int startPos = 0, endPos = 0;
- byte[] crnBuf = {'\r','\n'};
- for(endPos = 0; endPos.
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.swiftp.Defaults;
+
+import android.util.Log;
+
+public class CmdRETR extends FtpCmd implements Runnable {
+ //public static final String message = "TEMPLATE!!";
+ protected String input;
+
+ public CmdRETR(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdRETR.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "RETR executing");
+ String param = getParameter(input);
+ File fileToRetr;
+ String errString = null;
+
+ mainblock: {
+ fileToRetr = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
+ if(violatesChroot(fileToRetr)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ break mainblock;
+ } else if(fileToRetr.isDirectory()) {
+ myLog.l(Log.DEBUG, "Ignoring RETR for directory");
+ errString = "550 Can't RETR a directory\r\n";
+ break mainblock;
+ } else if(!fileToRetr.exists()) {
+ myLog.l(Log.INFO, "Can't RETR nonexistent file: " +
+ fileToRetr.getAbsolutePath());
+ errString = "550 File does not exist\r\n";
+ break mainblock;
+ } else if(!fileToRetr.canRead()) {
+ myLog.l(Log.INFO, "Failed RETR permission (canRead() is false)");
+ errString = "550 No read permissions\r\n";
+ break mainblock;
+ } /*else if(!sessionThread.isBinaryMode()) {
+ myLog.l(Log.INFO, "Failed RETR in text mode");
+ errString = "550 Text mode RETR not supported\r\n";
+ break mainblock;
+ }*/
+ FileInputStream in = null;
+ try {
+ in = new FileInputStream(fileToRetr);
+ byte[] buffer = new byte[Defaults.getDataChunkSize()];
+ int bytesRead;
+ if(sessionThread.startUsingDataSocket()) {
+ myLog.l(Log.DEBUG, "RETR opened data socket");
+ } else {
+ errString = "425 Error opening socket\r\n";
+ myLog.l(Log.INFO, "Error in initDataSocket()");
+ break mainblock;
+ }
+ sessionThread.writeString("150 Sending file\r\n");
+ if(sessionThread.isBinaryMode()) {
+ myLog.l(Log.DEBUG, "Transferring in binary mode");
+ while((bytesRead = in.read(buffer)) != -1) {
+ //myLog.l(Log.DEBUG,
+ // String.format("CmdRETR sending %d bytes", bytesRead));
+ if(sessionThread
+ .sendViaDataSocket(buffer, bytesRead) == false)
+ {
+ errString = "426 Data socket error\r\n";
+ myLog.l(Log.INFO, "Data socket error");
+ break mainblock;
+ }
+ }
+ } else { // We're in ASCII mode
+ myLog.l(Log.DEBUG, "Transferring in ASCII mode");
+ // We have to convert all solitary \n to \r\n
+ boolean lastBufEndedWithCR = false;
+ while((bytesRead = in.read(buffer)) != -1) {
+ int startPos = 0, endPos = 0;
+ byte[] crnBuf = {'\r','\n'};
+ for(endPos = 0; endPos.
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import android.util.Log;
-
-public class CmdRMD extends FtpCmd implements Runnable {
- public static final String message = "TEMPLATE!!";
- protected String input;
-
- public CmdRMD(SessionThread sessionThread, String input) {
- super(sessionThread, CmdRMD.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.l(Log.INFO, "RMD executing");
- String param = getParameter(input);
- File toRemove;
- String errString = null;
- mainblock: {
- if(param.length() < 1) { - errString = "550 Invalid argument\r\n"; - break mainblock; - } - toRemove = inputPathToChrootedFile(sessionThread.getWorkingDir(), param); - if(violatesChroot(toRemove)) { - errString = "550 Invalid name or chroot violation\r\n"; - break mainblock; - } - if(!toRemove.isDirectory()) { - errString = "550 Can't RMD a non-directory\r\n"; - break mainblock; - } - if(toRemove.equals(new File("/"))) { - errString = "550 Won't RMD the root directory\r\n"; - break mainblock; - } - if(!recursiveDelete(toRemove)) { - errString = "550 Deletion error, possibly incomplete\r\n"; - break mainblock; - } - } - if(errString != null) { - sessionThread.writeString(errString); - myLog.l(Log.INFO, "RMD failed: " + errString.trim()); - } else { - sessionThread.writeString("250 Removed directory\r\n"); - } - myLog.l(Log.DEBUG, "RMD finished"); - } - - /** - * Accepts a file or directory name, and recursively deletes the contents - * of that directory and all subdirectories. - * @param toDelete - * @return Whether the operation completed successfully - */ - protected boolean recursiveDelete(File toDelete) { - if(!toDelete.exists()) { - return false; - } - if(toDelete.isDirectory()) { - // If any of the recursive operations fail, then we return false - boolean success = true; - for(File entry : toDelete.listFiles()) { - success &= recursiveDelete(entry); - } - myLog.l(Log.DEBUG, "Recursively deleted: " + toDelete); - return success && toDelete.delete(); - } else { - myLog.l(Log.DEBUG, "RMD deleting file: " + toDelete); - return toDelete.delete(); - } - } -} +/* +Copyright 2009 David Revell + +This file is part of SwiFTP. + +SwiFTP is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +SwiFTP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import android.support.v4.provider.DocumentFile;
+import android.util.Log;
+
+import org.swiftp.Globals;
+
+import util.DocumentUtil;
+import util.FileUtil;
+
+public class CmdRMD extends FtpCmd implements Runnable {
+ public static final String message = "TEMPLATE!!";
+ protected String input;
+
+ public CmdRMD(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdRMD.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.INFO, "RMD executing");
+ String param = getParameter(input);
+ File toRemove;
+ String errString = null;
+ mainblock: {
+ if(param.length() < 1) { + errString = "550 Invalid argument\r\n"; + break mainblock; + } + toRemove = inputPathToChrootedFile(sessionThread.getWorkingDir(), param); + if(violatesChroot(toRemove)) { + errString = "550 Invalid name or chroot violation\r\n"; + break mainblock; + } + if(!toRemove.isDirectory()) { + errString = "550 Can't RMD a non-directory\r\n"; + break mainblock; + } + if(toRemove.equals(new File("/"))) { + errString = "550 Won't RMD the root directory\r\n"; + break mainblock; + } + if(!FileUtil.delete(toRemove)) { + errString = "550 Deletion error, possibly incomplete\r\n"; + break mainblock; + } + } + if(errString != null) { + sessionThread.writeString(errString); + myLog.l(Log.INFO, "RMD failed: " + errString.trim()); + } else { + sessionThread.writeString("250 Removed directory\r\n"); + } + myLog.l(Log.DEBUG, "RMD finished"); + } + + /* * + * Accepts a file or directory name, and recursively deletes the contents + * of that directory and all subdirectories. + * @param toDelete + * @return Whether the operation completed successfully + * / + protected boolean recursiveDelete(File toDelete) { + if(!toDelete.exists()) { + return false; + } + if(toDelete.isDirectory()) { + // If any of the recursive operations fail, then we return false + boolean success = true; + for(File entry : toDelete.listFiles()) { + success &= recursiveDelete(entry); + } + myLog.l(Log.DEBUG, "Recursively deleted: " + toDelete); + return success && toDelete.delete(); + } else { + myLog.l(Log.DEBUG, "RMD deleting file: " + toDelete); + return toDelete.delete(); + } + }*/ +} diff --git a/src/main/java/org/swiftp/server/CmdRNFR.java b/src/main/java/org/swiftp/server/CmdRNFR.java index e199c5660d7546bc5e73a2b1eb248c55b3e5f789..5a3c31c0c8e87cf8c0781dc6c362c127d0a59efe 100644 --- a/src/main/java/org/swiftp/server/CmdRNFR.java +++ b/src/main/java/org/swiftp/server/CmdRNFR.java @@ -1,58 +1,58 @@ -/* -Copyright 2009 David Revell - -This file is part of SwiFTP. - -SwiFTP is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -SwiFTP is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import android.util.Log;
-
-public class CmdRNFR extends FtpCmd implements Runnable {
- protected String input;
-
- public CmdRNFR(SessionThread sessionThread, String input) {
- super(sessionThread, CmdRNFR.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- String param = getParameter(input);
- String errString = null;
- File file = null;
- mainblock: {
- file = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
- if(violatesChroot(file)) {
- errString = "550 Invalid name or chroot violation\r\n";
- break mainblock;
- }
- if(!file.exists()) {
- errString = "450 Cannot rename nonexistent file\r\n";
- }
- }
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.l(Log.INFO, "RNFR failed: " + errString.trim());
- sessionThread.setRenameFrom(null);
- } else {
- sessionThread.writeString("350 Filename noted, now send RNTO\r\n");
- sessionThread.setRenameFrom(file);
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import android.util.Log;
+
+public class CmdRNFR extends FtpCmd implements Runnable {
+ protected String input;
+
+ public CmdRNFR(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdRNFR.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ String param = getParameter(input);
+ String errString = null;
+ File file = null;
+ mainblock: {
+ file = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
+ if(violatesChroot(file)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ break mainblock;
+ }
+ if(!file.exists()) {
+ errString = "450 Cannot rename nonexistent file\r\n";
+ }
+ }
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.l(Log.INFO, "RNFR failed: " + errString.trim());
+ sessionThread.setRenameFrom(null);
+ } else {
+ sessionThread.writeString("350 Filename noted, now send RNTO\r\n");
+ sessionThread.setRenameFrom(file);
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdRNTO.java b/src/main/java/org/swiftp/server/CmdRNTO.java
index f2ccfe8bd18be2d54de141197f785e9708bc1e83..ceb244e72013623851533b528a229e22cd189a92 100644
--- a/src/main/java/org/swiftp/server/CmdRNTO.java
+++ b/src/main/java/org/swiftp/server/CmdRNTO.java
@@ -1,67 +1,73 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.File;
-
-import android.util.Log;
-
-public class CmdRNTO extends FtpCmd implements Runnable {
- protected String input;
-
- public CmdRNTO(SessionThread sessionThread, String input) {
- super(sessionThread, CmdRNTO.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- String param = getParameter(input);
- String errString = null;
- File toFile = null;
- myLog.l(Log.DEBUG, "RNTO executing\r\n");
- mainblock: {
- myLog.l(Log.INFO, "param: " + param);
- toFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
- myLog.l(Log.INFO, "RNTO parsed: " + toFile.getPath());
- if(violatesChroot(toFile)) {
- errString = "550 Invalid name or chroot violation\r\n";
- break mainblock;
- }
- File fromFile = sessionThread.getRenameFrom();
- if(fromFile == null) {
- errString = "550 Rename error, maybe RNFR not sent\r\n";
- break mainblock;
- }
- if(!fromFile.renameTo(toFile)) {
- errString = "550 Error during rename operation\r\n";
- break mainblock;
- }
- }
- if(errString != null) {
- sessionThread.writeString(errString);
- myLog.l(Log.INFO, "RNFR failed: " + errString.trim());
- } else {
- sessionThread.writeString("250 rename successful\r\n");
- }
- sessionThread.setRenameFrom(null);
- myLog.l(Log.DEBUG, "RNTO finished");
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.File;
+
+import android.support.v4.provider.DocumentFile;
+import android.util.Log;
+
+import org.swiftp.Globals;
+
+import util.DocumentUtil;
+import util.FileUtil;
+
+public class CmdRNTO extends FtpCmd implements Runnable {
+ protected String input;
+
+ public CmdRNTO(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdRNTO.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ String param = getParameter(input);
+ String errString = null;
+ File toFile = null;
+ myLog.l(Log.DEBUG, "RNTO executing\r\n");
+ mainblock: {
+ myLog.l(Log.INFO, "param: " + param);
+ toFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
+ myLog.l(Log.INFO, "RNTO parsed: " + toFile.getPath());
+ if(violatesChroot(toFile)) {
+ errString = "550 Invalid name or chroot violation\r\n";
+ break mainblock;
+ }
+ File fromFile = sessionThread.getRenameFrom();
+ if(fromFile == null) {
+ errString = "550 Rename error, maybe RNFR not sent\r\n";
+ break mainblock;
+ }
+ if(!FileUtil.rename(fromFile,toFile)) {
+ errString = "550 Error during rename operation\r\n";
+ break mainblock;
+ }
+ }
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ myLog.l(Log.INFO, "RNFR failed: " + errString.trim());
+ } else {
+ sessionThread.writeString("250 rename successful\r\n");
+ }
+ sessionThread.setRenameFrom(null);
+ myLog.l(Log.DEBUG, "RNTO finished");
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdSIZE.java b/src/main/java/org/swiftp/server/CmdSIZE.java
index 25507a59317a75d145c6515bd4964f1598278a74..933beb54f7df713d39329c554a8426a9fcfdb760 100644
--- a/src/main/java/org/swiftp/server/CmdSIZE.java
+++ b/src/main/java/org/swiftp/server/CmdSIZE.java
@@ -1,56 +1,56 @@
-package org.swiftp.server;
-
-import java.io.File;
-import java.io.IOException;
-
-public class CmdSIZE extends FtpCmd {
- protected String input;
-
- public CmdSIZE(SessionThread sessionThread, String input) {
- super(sessionThread, CmdSIZE.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- myLog.d("SIZE executing");
-
- String errString = null;
- String param = getParameter(input);
- long size = 0;
- mainblock: {
- File currentDir = sessionThread.getWorkingDir();
- if(param.contains(File.separator)) {
- errString = "550 No directory traversal allowed in SIZE param\r\n";
- break mainblock;
- }
- File target = new File(currentDir, param);
-
- // We should have caught any invalid location access before now, but
- // here we check again, just to be explicitly sure.
- if(violatesChroot(target)) {
- errString = "550 SIZE target violates chroot\r\n";
- break mainblock;
- }
- if(!target.exists()) {
- errString = "550 Cannot get the SIZE of nonexistent object\r\n";
- try {
- myLog.i("Failed getting size of: " + target.getCanonicalPath());
- } catch (IOException e) {}
- break mainblock;
- }
- if(!target.isFile()) {
- errString = "550 Cannot get the size of a non-file\r\n";
- break mainblock;
- }
- size = target.length();
- }
- if(errString != null) {
- sessionThread.writeString(errString);
- } else {
- sessionThread.writeString("213 " + size + "\r\n");
- }
- myLog.d("SIZE complete");
- }
-
-}
+package org.swiftp.server;
+
+import java.io.File;
+import java.io.IOException;
+
+public class CmdSIZE extends FtpCmd {
+ protected String input;
+
+ public CmdSIZE(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdSIZE.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ myLog.d("SIZE executing");
+
+ String errString = null;
+ String param = getParameter(input);
+ long size = 0;
+ mainblock: {
+ File currentDir = sessionThread.getWorkingDir();
+ if(param.contains(File.separator)) {
+ errString = "550 No directory traversal allowed in SIZE param\r\n";
+ break mainblock;
+ }
+ File target = new File(currentDir, param);
+
+ // We should have caught any invalid location access before now, but
+ // here we check again, just to be explicitly sure.
+ if(violatesChroot(target)) {
+ errString = "550 SIZE target violates chroot\r\n";
+ break mainblock;
+ }
+ if(!target.exists()) {
+ errString = "550 Cannot get the SIZE of nonexistent object\r\n";
+ try {
+ myLog.i("Failed getting size of: " + target.getCanonicalPath());
+ } catch (IOException e) {}
+ break mainblock;
+ }
+ if(!target.isFile()) {
+ errString = "550 Cannot get the size of a non-file\r\n";
+ break mainblock;
+ }
+ size = target.length();
+ }
+ if(errString != null) {
+ sessionThread.writeString(errString);
+ } else {
+ sessionThread.writeString("213 " + size + "\r\n");
+ }
+ myLog.d("SIZE complete");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdSTOR.java b/src/main/java/org/swiftp/server/CmdSTOR.java
index ce3f11edcd6370667ed42c8de60eae14135e00e8..f5015883c9be368ea0f784566ebfdb042e187332 100644
--- a/src/main/java/org/swiftp/server/CmdSTOR.java
+++ b/src/main/java/org/swiftp/server/CmdSTOR.java
@@ -1,35 +1,35 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-
-public class CmdSTOR extends CmdAbstractStore implements Runnable {
- protected String input;
-
- public CmdSTOR(SessionThread sessionThread, String input) {
- super(sessionThread, CmdSTOR.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- doStorOrAppe(getParameter(input), false);
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+
+public class CmdSTOR extends CmdAbstractStore implements Runnable {
+ protected String input;
+
+ public CmdSTOR(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdSTOR.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ doStorOrAppe(getParameter(input), false);
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdSYST.java b/src/main/java/org/swiftp/server/CmdSYST.java
index 4103edf1a05c33e03a049ff35bc57cfb66e57a3f..39b1fc91f689acefc133b16ac97e084451371edf 100644
--- a/src/main/java/org/swiftp/server/CmdSYST.java
+++ b/src/main/java/org/swiftp/server/CmdSYST.java
@@ -1,40 +1,40 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import android.util.Log;
-
-public class CmdSYST extends FtpCmd implements Runnable {
- // This is considered a safe response to the SYST command, see
- // http://cr.yp.to/ftp/syst.html
- public static final String response = "215 UNIX Type: L8\r\n";
-
- public CmdSYST(SessionThread sessionThread, String input) {
- super(sessionThread, CmdSYST.class.toString());
- }
-
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "SYST executing");
- sessionThread.writeString(response);
- myLog.l(Log.DEBUG, "SYST finished");
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import android.util.Log;
+
+public class CmdSYST extends FtpCmd implements Runnable {
+ // This is considered a safe response to the SYST command, see
+ // http://cr.yp.to/ftp/syst.html
+ public static final String response = "215 UNIX Type: L8\r\n";
+
+ public CmdSYST(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdSYST.class.toString());
+ }
+
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "SYST executing");
+ sessionThread.writeString(response);
+ myLog.l(Log.DEBUG, "SYST finished");
+ }
+}
diff --git a/src/main/java/org/swiftp/server/CmdTYPE.java b/src/main/java/org/swiftp/server/CmdTYPE.java
index d2d3fb7a4c4eb596d492a87e78fb32eda818b8dd..e60c66cca79c180fd9aadf78dc9252349eb0540c 100644
--- a/src/main/java/org/swiftp/server/CmdTYPE.java
+++ b/src/main/java/org/swiftp/server/CmdTYPE.java
@@ -1,50 +1,50 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import android.util.Log;
-
-public class CmdTYPE extends FtpCmd implements Runnable {
- String input;
-
- public CmdTYPE(SessionThread sessionThread, String input) {
- super(sessionThread, CmdTYPE.class.toString());
- this.input = input;
- }
-
- @Override
- public void run() {
- String output;
- myLog.l(Log.DEBUG, "TYPE executing");
- String param = getParameter(input);
- if(param.equals("I") || param.equals("L 8")) {
- output = "200 Binary type set\r\n";
- sessionThread.setBinaryMode(true);
- } else if (param.equals("A") || param.equals("A N")) {
- output = "200 ASCII type set\r\n";
- sessionThread.setBinaryMode(false);
- } else {
- output = "503 Malformed TYPE command\r\n";
- }
- sessionThread.writeString(output);
- myLog.l(Log.DEBUG, "TYPE complete");
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import android.util.Log;
+
+public class CmdTYPE extends FtpCmd implements Runnable {
+ String input;
+
+ public CmdTYPE(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdTYPE.class.toString());
+ this.input = input;
+ }
+
+ @Override
+ public void run() {
+ String output;
+ myLog.l(Log.DEBUG, "TYPE executing");
+ String param = getParameter(input);
+ if(param.equals("I") || param.equals("L 8")) {
+ output = "200 Binary type set\r\n";
+ sessionThread.setBinaryMode(true);
+ } else if (param.equals("A") || param.equals("A N")) {
+ output = "200 ASCII type set\r\n";
+ sessionThread.setBinaryMode(false);
+ } else {
+ output = "503 Malformed TYPE command\r\n";
+ }
+ sessionThread.writeString(output);
+ myLog.l(Log.DEBUG, "TYPE complete");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdTemplate.java b/src/main/java/org/swiftp/server/CmdTemplate.java
index e56ac27bf7d431b058b957ab69b6e45e8f4d74d3..4f9a4b96b25857f75256c1887c8a97cf63c50e96 100644
--- a/src/main/java/org/swiftp/server/CmdTemplate.java
+++ b/src/main/java/org/swiftp/server/CmdTemplate.java
@@ -1,37 +1,37 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import android.util.Log;
-
-public class CmdTemplate extends FtpCmd implements Runnable {
- public static final String message = "TEMPLATE!!";
-
- public CmdTemplate(SessionThread sessionThread, String input) {
- super(sessionThread, CmdTemplate.class.toString());
- }
-
- @Override
- public void run() {
- sessionThread.writeString(message);
- myLog.l(Log.INFO, "Template log message");
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import android.util.Log;
+
+public class CmdTemplate extends FtpCmd implements Runnable {
+ public static final String message = "TEMPLATE!!";
+
+ public CmdTemplate(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdTemplate.class.toString());
+ }
+
+ @Override
+ public void run() {
+ sessionThread.writeString(message);
+ myLog.l(Log.INFO, "Template log message");
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/CmdUSER.java b/src/main/java/org/swiftp/server/CmdUSER.java
index 88cf60c32593e2543501407a024ad04ba178a359..338fc92479d812eacebd4a0045dfe00d28433e79 100644
--- a/src/main/java/org/swiftp/server/CmdUSER.java
+++ b/src/main/java/org/swiftp/server/CmdUSER.java
@@ -1,45 +1,45 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import android.util.Log;
-
-public class CmdUSER extends FtpCmd implements Runnable {
- protected String input;
-
- public CmdUSER(SessionThread sessionThread, String input) {
- super(sessionThread, CmdUSER.class.toString());
- this.input = input;
-
- }
-
- @Override
- public void run() {
- myLog.l(Log.DEBUG, "USER executing");
- String username = FtpCmd.getParameter(input);
- if(!username.matches("[A-Za-z0-9]+")) {
- sessionThread.writeString("530 Invalid username\r\n");
- return;
- }
- sessionThread.writeString("331 Send password\r\n");
- sessionThread.account.setUsername(username);
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import android.util.Log;
+
+public class CmdUSER extends FtpCmd implements Runnable {
+ protected String input;
+
+ public CmdUSER(SessionThread sessionThread, String input) {
+ super(sessionThread, CmdUSER.class.toString());
+ this.input = input;
+
+ }
+
+ @Override
+ public void run() {
+ myLog.l(Log.DEBUG, "USER executing");
+ String username = FtpCmd.getParameter(input);
+ if(!username.matches("[A-Za-z0-9]+")) {
+ sessionThread.writeString("530 Invalid username\r\n");
+ return;
+ }
+ sessionThread.writeString("331 Send password\r\n");
+ sessionThread.account.setUsername(username);
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/DataSocketFactory.java b/src/main/java/org/swiftp/server/DataSocketFactory.java
index 23d27d07dccc633f0243a6577daa8694b0df04db..1a25c824741b63097b9536d3d2e808f6568b66ed 100644
--- a/src/main/java/org/swiftp/server/DataSocketFactory.java
+++ b/src/main/java/org/swiftp/server/DataSocketFactory.java
@@ -1,60 +1,60 @@
-package org.swiftp.server;
-
-import java.net.InetAddress;
-import java.net.Socket;
-
-import org.swiftp.MyLog;
-
-
-abstract public class DataSocketFactory {
-
- /**
- * A DataSocketFactory hides the implementation of the opening and closing
- * of the data sockets which are used to transmit directory listings and
- * file contents. This is necessary because normal FTP data sockets are
- * opened and closed very differently from the abnormal sort of data sockets
- * we use in conjunction with our proxy system.
- */
- protected MyLog myLog = new MyLog(getClass().getName());
-
- /**
- * When SwiFTP receives a PORT command, this will be called. Subclasses should
- * perform whatever initialization is necessary.
- * @return Whether the necessary actions completed successfully
- */
- abstract public boolean onPort(InetAddress dest, int port);
-
- /**
- * When SwiFTP receives a PASV command, this will be called. Subclasses should
- * perform whatever initialization is necessary.
- * @return Whether the necessary actions completed successfully
- */
- abstract public int onPasv();
-
- /**
- * When it's time for data transfer to begin, the SessionThread will call this
- * method to perform any necessary actions to prepare the Socket for use and
- * return it in a state that's ready for reading or writing.
- * @return The opened Socket
- */
- abstract public Socket onTransfer();
-
- /**
- * Sometimes we'll need to know the IP address at which we can be contacted. For
- * instance, the response to a PASV command will be the IP and port that the
- * client should use to connect it's data socket.
- */
- abstract public InetAddress getPasvIp();
-
- /**
- * We sometimes want to track the total number of bytes that go over the
- * command and data sockets. The SessionThread can call this function to
- * reports its usage, and different DataSocketFactory subclasses can
- * handle the data however is appropriate. For the ProxyDataSocketFactory,
- * we want to present the total to the user in the UI to guilt them into
- * donating.
- * @param numBytes the number of bytes to add to the total
- */
- abstract public void reportTraffic(long numBytes);
-}
-
+package org.swiftp.server;
+
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.swiftp.MyLog;
+
+
+abstract public class DataSocketFactory {
+
+ /**
+ * A DataSocketFactory hides the implementation of the opening and closing
+ * of the data sockets which are used to transmit directory listings and
+ * file contents. This is necessary because normal FTP data sockets are
+ * opened and closed very differently from the abnormal sort of data sockets
+ * we use in conjunction with our proxy system.
+ */
+ protected MyLog myLog = new MyLog(getClass().getName());
+
+ /**
+ * When SwiFTP receives a PORT command, this will be called. Subclasses should
+ * perform whatever initialization is necessary.
+ * @return Whether the necessary actions completed successfully
+ */
+ abstract public boolean onPort(InetAddress dest, int port);
+
+ /**
+ * When SwiFTP receives a PASV command, this will be called. Subclasses should
+ * perform whatever initialization is necessary.
+ * @return Whether the necessary actions completed successfully
+ */
+ abstract public int onPasv();
+
+ /**
+ * When it's time for data transfer to begin, the SessionThread will call this
+ * method to perform any necessary actions to prepare the Socket for use and
+ * return it in a state that's ready for reading or writing.
+ * @return The opened Socket
+ */
+ abstract public Socket onTransfer();
+
+ /**
+ * Sometimes we'll need to know the IP address at which we can be contacted. For
+ * instance, the response to a PASV command will be the IP and port that the
+ * client should use to connect it's data socket.
+ */
+ abstract public InetAddress getPasvIp();
+
+ /**
+ * We sometimes want to track the total number of bytes that go over the
+ * command and data sockets. The SessionThread can call this function to
+ * reports its usage, and different DataSocketFactory subclasses can
+ * handle the data however is appropriate. For the ProxyDataSocketFactory,
+ * we want to present the total to the user in the UI to guilt them into
+ * donating.
+ * @param numBytes the number of bytes to add to the total
+ */
+ abstract public void reportTraffic(long numBytes);
+}
+
diff --git a/src/main/java/org/swiftp/server/FtpCmd.java b/src/main/java/org/swiftp/server/FtpCmd.java
index f56f576b2f21acadfb59eb3871f0ef094742992f..615e35fbdec05b006fe59b9a6a7bfd5ab83bc49f 100644
--- a/src/main/java/org/swiftp/server/FtpCmd.java
+++ b/src/main/java/org/swiftp/server/FtpCmd.java
@@ -1,207 +1,207 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-
-import java.io.File;
-import java.lang.reflect.Constructor;
-
-import org.swiftp.Globals;
-import org.swiftp.MyLog;
-
-import android.util.Log;
-
-public abstract class FtpCmd implements Runnable {
- protected SessionThread sessionThread;
- protected MyLog myLog;
- protected static MyLog staticLog = new MyLog(FtpCmd.class.toString());
-
- protected static CmdMap[] cmdClasses = {
- new CmdMap("SYST", CmdSYST.class),
- new CmdMap("USER", CmdUSER.class),
- new CmdMap("PASS", CmdPASS.class),
- new CmdMap("TYPE", CmdTYPE.class),
- new CmdMap("CWD", CmdCWD.class),
- new CmdMap("PWD", CmdPWD.class),
- new CmdMap("LIST", CmdLIST.class),
- new CmdMap("PASV", CmdPASV.class),
- new CmdMap("RETR", CmdRETR.class),
- new CmdMap("NLST", CmdNLST.class),
- new CmdMap("NOOP", CmdNOOP.class),
- new CmdMap("STOR", CmdSTOR.class),
- new CmdMap("DELE", CmdDELE.class),
- new CmdMap("RNFR", CmdRNFR.class),
- new CmdMap("RNTO", CmdRNTO.class),
- new CmdMap("RMD", CmdRMD.class),
- new CmdMap("MKD", CmdMKD.class),
- new CmdMap("OPTS", CmdOPTS.class),
- new CmdMap("PORT", CmdPORT.class),
- new CmdMap("QUIT", CmdQUIT.class),
- new CmdMap("FEAT", CmdFEAT.class),
- new CmdMap("SIZE", CmdSIZE.class),
- new CmdMap("CDUP", CmdCDUP.class),
- new CmdMap("APPE", CmdAPPE.class),
- new CmdMap("XCUP", CmdCDUP.class), // synonym
- new CmdMap("XPWD", CmdPWD.class), // synonym
- new CmdMap("XMKD", CmdMKD.class), // synonym
- new CmdMap("XRMD", CmdRMD.class) // synonym
- };
-
- public FtpCmd(SessionThread sessionThread, String logName) {
- this.sessionThread = sessionThread;
- myLog = new MyLog(logName);
- }
-
- abstract public void run();
-
- protected static void dispatchCommand(SessionThread session,
- String inputString) {
- String[] strings = inputString.split(" ");
- String unrecognizedCmdMsg = "502 Command not recognized\r\n";
- if(strings == null) {
- // There was some egregious sort of parsing error
- String errString = "502 Command parse error\r\n";
- staticLog.l(Log.INFO, errString);
- session.writeString(errString);
- return;
- }
- if(strings.length < 1) { - staticLog.l(Log.INFO, "No strings parsed"); - session.writeString(unrecognizedCmdMsg); - return; - } - String verb = strings[0]; - if(verb.length() < 1) { - staticLog.l(Log.INFO, "Invalid command verb"); - session.writeString(unrecognizedCmdMsg); - return; - } - FtpCmd cmdInstance = null; - verb = verb.trim(); - verb = verb.toUpperCase(); - for(int i=0; i constructor;
- try {
- constructor = cmdClasses[i].getCommand().getConstructor(
- new Class[] {SessionThread.class, String.class});
- } catch (NoSuchMethodException e) {
- staticLog.l(Log.ERROR, "FtpCmd subclass lacks expected " +
- "constructor ");
- return;
- }
- try {
- cmdInstance = constructor.newInstance(
- new Object[] {session, inputString});
- } catch(Exception e) {
- staticLog.l(Log.ERROR,
- "Instance creation error on FtpCmd");
- return;
- }
- }
- }
- if(cmdInstance == null) {
- // If we couldn't find a matching command,
- staticLog.l(Log.DEBUG, "Ignoring unrecognized FTP verb: " + verb);
- session.writeString(unrecognizedCmdMsg);
- return;
- } else if(session.isAuthenticated()
- || cmdInstance.getClass().equals(CmdUSER.class)
- || cmdInstance.getClass().equals(CmdPASS.class)
- || cmdInstance.getClass().equals(CmdUSER.class))
- {
- // Unauthenticated users can run only USER, PASS and QUIT
- cmdInstance.run();
- } else {
- session.writeString("530 Login first with USER and PASS\r\n");
- }
- }
-
- /**
- * An FTP parameter is that part of the input string that occurs
- * after the first space, including any subsequent spaces. Also,
- * we want to chop off the trailing '\r\n', if present.
- *
- * Some parameters shouldn't be logged or output (e.g. passwords),
- * so the caller can use silent==true in that case.
- */
- static public String getParameter(String input, boolean silent) {
- if(input == null) {
- return "";
- }
- int firstSpacePosition = input.indexOf(' ');
- if(firstSpacePosition == -1) {
- return "";
- }
- String retString = input.substring(firstSpacePosition+1);
-
- // Remove trailing whitespace
- // todo: trailing whitespace may be significant, just remove \r\n
- retString = retString.replaceAll("\\s+$", "");
-
- if(!silent) {
- staticLog.l(Log.DEBUG, "Parsed argument: " + retString);
- }
- return retString;
- }
-
- /**
- * A wrapper around getParameter, for when we don't want it to be silent.
- */
- static public String getParameter(String input) {
- return getParameter(input, false);
- }
-
- public static File inputPathToChrootedFile(File existingPrefix, String param) {
- try {
- if(param.charAt(0) == '/') {
- // The STOR contained an absolute path
- File chroot = Globals.getChrootDir();
- return new File(chroot, param);
- }
- } catch (Exception e) {}
-
- // The STOR contained a relative path
- return new File(existingPrefix, param);
- }
-
- public boolean violatesChroot(File file) {
- File chroot = Globals.getChrootDir();
- try {
- String canonicalPath = file.getCanonicalPath();
- if(!canonicalPath.startsWith(chroot.toString())) {
- myLog.l(Log.INFO, "Path violated folder restriction, denying");
- myLog.l(Log.DEBUG, "path: " + canonicalPath);
- myLog.l(Log.DEBUG, "chroot: " + chroot.toString());
- return true; // the path must begin with the chroot path
- }
- return false;
- } catch(Exception e) {
- myLog.l(Log.INFO, "Path canonicalization problem: " + e.toString());
- myLog.l(Log.INFO, "When checking file: " + file.getAbsolutePath());
- return true; // for security, assume violation
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+
+import org.swiftp.Globals;
+import org.swiftp.MyLog;
+
+import android.util.Log;
+
+public abstract class FtpCmd implements Runnable {
+ protected SessionThread sessionThread;
+ protected MyLog myLog;
+ protected static MyLog staticLog = new MyLog(FtpCmd.class.toString());
+
+ protected static CmdMap[] cmdClasses = {
+ new CmdMap("SYST", CmdSYST.class),
+ new CmdMap("USER", CmdUSER.class),
+ new CmdMap("PASS", CmdPASS.class),
+ new CmdMap("TYPE", CmdTYPE.class),
+ new CmdMap("CWD", CmdCWD.class),
+ new CmdMap("PWD", CmdPWD.class),
+ new CmdMap("LIST", CmdLIST.class),
+ new CmdMap("PASV", CmdPASV.class),
+ new CmdMap("RETR", CmdRETR.class),
+ new CmdMap("NLST", CmdNLST.class),
+ new CmdMap("NOOP", CmdNOOP.class),
+ new CmdMap("STOR", CmdSTOR.class),
+ new CmdMap("DELE", CmdDELE.class),
+ new CmdMap("RNFR", CmdRNFR.class),
+ new CmdMap("RNTO", CmdRNTO.class),
+ new CmdMap("RMD", CmdRMD.class),
+ new CmdMap("MKD", CmdMKD.class),
+ new CmdMap("OPTS", CmdOPTS.class),
+ new CmdMap("PORT", CmdPORT.class),
+ new CmdMap("QUIT", CmdQUIT.class),
+ new CmdMap("FEAT", CmdFEAT.class),
+ new CmdMap("SIZE", CmdSIZE.class),
+ new CmdMap("CDUP", CmdCDUP.class),
+ new CmdMap("APPE", CmdAPPE.class),
+ new CmdMap("XCUP", CmdCDUP.class), // synonym
+ new CmdMap("XPWD", CmdPWD.class), // synonym
+ new CmdMap("XMKD", CmdMKD.class), // synonym
+ new CmdMap("XRMD", CmdRMD.class) // synonym
+ };
+
+ public FtpCmd(SessionThread sessionThread, String logName) {
+ this.sessionThread = sessionThread;
+ myLog = new MyLog(logName);
+ }
+
+ abstract public void run();
+
+ protected static void dispatchCommand(SessionThread session,
+ String inputString) {
+ String[] strings = inputString.split(" ");
+ String unrecognizedCmdMsg = "502 Command not recognized\r\n";
+ if(strings == null) {
+ // There was some egregious sort of parsing error
+ String errString = "502 Command parse error\r\n";
+ staticLog.l(Log.INFO, errString);
+ session.writeString(errString);
+ return;
+ }
+ if(strings.length < 1) { + staticLog.l(Log.INFO, "No strings parsed"); + session.writeString(unrecognizedCmdMsg); + return; + } + String verb = strings[0]; + if(verb.length() < 1) { + staticLog.l(Log.INFO, "Invalid command verb"); + session.writeString(unrecognizedCmdMsg); + return; + } + FtpCmd cmdInstance = null; + verb = verb.trim(); + verb = verb.toUpperCase(); + for(int i=0; i constructor;
+ try {
+ constructor = cmdClasses[i].getCommand().getConstructor(
+ new Class[] {SessionThread.class, String.class});
+ } catch (NoSuchMethodException e) {
+ staticLog.l(Log.ERROR, "FtpCmd subclass lacks expected " +
+ "constructor ");
+ return;
+ }
+ try {
+ cmdInstance = constructor.newInstance(
+ new Object[] {session, inputString});
+ } catch(Exception e) {
+ staticLog.l(Log.ERROR,
+ "Instance creation error on FtpCmd");
+ return;
+ }
+ }
+ }
+ if(cmdInstance == null) {
+ // If we couldn't find a matching command,
+ staticLog.l(Log.DEBUG, "Ignoring unrecognized FTP verb: " + verb);
+ session.writeString(unrecognizedCmdMsg);
+ return;
+ } else if(session.isAuthenticated()
+ || cmdInstance.getClass().equals(CmdUSER.class)
+ || cmdInstance.getClass().equals(CmdPASS.class)
+ || cmdInstance.getClass().equals(CmdUSER.class))
+ {
+ // Unauthenticated users can run only USER, PASS and QUIT
+ cmdInstance.run();
+ } else {
+ session.writeString("530 Login first with USER and PASS\r\n");
+ }
+ }
+
+ /**
+ * An FTP parameter is that part of the input string that occurs
+ * after the first space, including any subsequent spaces. Also,
+ * we want to chop off the trailing '\r\n', if present.
+ *
+ * Some parameters shouldn't be logged or output (e.g. passwords),
+ * so the caller can use silent==true in that case.
+ */
+ static public String getParameter(String input, boolean silent) {
+ if(input == null) {
+ return "";
+ }
+ int firstSpacePosition = input.indexOf(' ');
+ if(firstSpacePosition == -1) {
+ return "";
+ }
+ String retString = input.substring(firstSpacePosition+1);
+
+ // Remove trailing whitespace
+ // todo: trailing whitespace may be significant, just remove \r\n
+ retString = retString.replaceAll("\\s+$", "");
+
+ if(!silent) {
+ staticLog.l(Log.DEBUG, "Parsed argument: " + retString);
+ }
+ return retString;
+ }
+
+ /**
+ * A wrapper around getParameter, for when we don't want it to be silent.
+ */
+ static public String getParameter(String input) {
+ return getParameter(input, false);
+ }
+
+ public static File inputPathToChrootedFile(File existingPrefix, String param) {
+ try {
+ if(param.charAt(0) == '/') {
+ // The STOR contained an absolute path
+ File chroot = Globals.getChrootDir();
+ return new File(chroot, param);
+ }
+ } catch (Exception e) {}
+
+ // The STOR contained a relative path
+ return new File(existingPrefix, param);
+ }
+
+ public boolean violatesChroot(File file) {
+ File chroot = Globals.getChrootDir();
+ try {
+ String canonicalPath = file.getCanonicalPath();
+ if(!canonicalPath.startsWith(chroot.toString())) {
+ myLog.l(Log.INFO, "Path violated folder restriction, denying");
+ myLog.l(Log.DEBUG, "path: " + canonicalPath);
+ myLog.l(Log.DEBUG, "chroot: " + chroot.toString());
+ return true; // the path must begin with the chroot path
+ }
+ return false;
+ } catch(Exception e) {
+ myLog.l(Log.INFO, "Path canonicalization problem: " + e.toString());
+ myLog.l(Log.INFO, "When checking file: " + file.getAbsolutePath());
+ return true; // for security, assume violation
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/NormalDataSocketFactory.java b/src/main/java/org/swiftp/server/NormalDataSocketFactory.java
index e6a2735b10e6e83544d92936c5a31b9f50881f74..a967a660260fbc2ef26fc02d9b7368a749514482 100644
--- a/src/main/java/org/swiftp/server/NormalDataSocketFactory.java
+++ b/src/main/java/org/swiftp/server/NormalDataSocketFactory.java
@@ -1,161 +1,161 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.ServerSocket;
-import java.net.Socket;
-
-import org.swiftp.Defaults;
-import org.swiftp.FTPServerService;
-
-import android.util.Log;
-
-public class NormalDataSocketFactory extends DataSocketFactory {
- /**
- * This class implements normal, traditional opening and closing of data sockets
- * used for transmitting directory listings and file contents. PORT and PASV
- * work according to the FTP specs. This is in contrast to a
- * ProxyDataSocketFactory, which performs contortions to allow data sockets
- * to be proxied through a server out in the cloud.
- *
- */
-
- // Listener socket used for PASV mode
- ServerSocket server = null;
- // Remote IP & port information used for PORT mode
- InetAddress remoteAddr;
- int remotePort;
- boolean isPasvMode = true;
-
- public NormalDataSocketFactory() {
- clearState();
- }
-
-
- private void clearState() {
- /**
- * Clears the state of this object, as if no pasv() or port() had occurred.
- * All sockets are closed.
- */
- if(server != null) {
- try {
- server.close();
- } catch (IOException e) {}
- }
- server = null;
- remoteAddr = null;
- remotePort = 0;
- myLog.l(Log.DEBUG, "NormalDataSocketFactory state cleared");
- }
-
- @Override
- public int onPasv() {
- clearState();
- try {
- // Listen on any port (port parameter 0)
- server = new ServerSocket(0, Defaults.tcpConnectionBacklog);
- myLog.l(Log.DEBUG, "Data socket pasv() listen successful");
- return server.getLocalPort();
- } catch(IOException e) {
- myLog.l(Log.ERROR, "Data socket creation error");
- clearState();
- return 0;
- }
- }
-
- @Override
- public boolean onPort(InetAddress remoteAddr, int remotePort) {
- clearState();
- this.remoteAddr = remoteAddr;
- this.remotePort = remotePort;
- return true;
- }
-
- @Override
- public Socket onTransfer() {
- if(server == null) {
- // We're in PORT mode (not PASV)
- if(remoteAddr == null || remotePort == 0) {
- myLog.l(Log.INFO, "PORT mode but not initialized correctly");
- clearState();
- return null;
- }
- Socket socket;
- try {
- socket = new Socket(remoteAddr, remotePort);
- } catch (IOException e) {
- myLog.l(Log.INFO,
- "Couldn't open PORT data socket to: " +
- remoteAddr.toString() + ":" + remotePort);
- clearState();
- return null;
- }
-
- // Kill the socket if nothing happens for X milliseconds
- try {
- socket.setSoTimeout(Defaults.SO_TIMEOUT_MS);
- } catch (Exception e) {
- myLog.l(Log.ERROR, "Couldn't set SO_TIMEOUT");
- clearState();
- return null;
- }
-
- return socket;
- } else {
- // We're in PASV mode (not PORT)
- Socket socket = null;
- try {
- socket = server.accept();
- myLog.l(Log.DEBUG, "onTransfer pasv accept successful");
- } catch (Exception e) {
- myLog.l(Log.INFO, "Exception accepting PASV socket");
- socket = null;
- }
- clearState();
- return socket; // will be null if error occurred
- }
- }
-
- /**
- * Return the port number that the remote client should be informed of (in the body
- * of the PASV response).
- * @return The port number, or -1 if error.
- */
- public int getPortNumber() {
- if(server != null) {
- return server.getLocalPort(); // returns -1 if serversocket is unbound
- } else {
- return -1;
- }
- }
-
- @Override
- public InetAddress getPasvIp() {
- //String retVal = server.getInetAddress().getHostAddress();
- return FTPServerService.getWifiIp();
- }
-
- @Override
- public void reportTraffic(long bytes) {
- // ignore, we don't care about how much traffic goes over wifi.
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.swiftp.Defaults;
+import org.swiftp.FTPServerService;
+
+import android.util.Log;
+
+public class NormalDataSocketFactory extends DataSocketFactory {
+ /**
+ * This class implements normal, traditional opening and closing of data sockets
+ * used for transmitting directory listings and file contents. PORT and PASV
+ * work according to the FTP specs. This is in contrast to a
+ * ProxyDataSocketFactory, which performs contortions to allow data sockets
+ * to be proxied through a server out in the cloud.
+ *
+ */
+
+ // Listener socket used for PASV mode
+ ServerSocket server = null;
+ // Remote IP & port information used for PORT mode
+ InetAddress remoteAddr;
+ int remotePort;
+ boolean isPasvMode = true;
+
+ public NormalDataSocketFactory() {
+ clearState();
+ }
+
+
+ private void clearState() {
+ /**
+ * Clears the state of this object, as if no pasv() or port() had occurred.
+ * All sockets are closed.
+ */
+ if(server != null) {
+ try {
+ server.close();
+ } catch (IOException e) {}
+ }
+ server = null;
+ remoteAddr = null;
+ remotePort = 0;
+ myLog.l(Log.DEBUG, "NormalDataSocketFactory state cleared");
+ }
+
+ @Override
+ public int onPasv() {
+ clearState();
+ try {
+ // Listen on any port (port parameter 0)
+ server = new ServerSocket(0, Defaults.tcpConnectionBacklog);
+ myLog.l(Log.DEBUG, "Data socket pasv() listen successful");
+ return server.getLocalPort();
+ } catch(IOException e) {
+ myLog.l(Log.ERROR, "Data socket creation error");
+ clearState();
+ return 0;
+ }
+ }
+
+ @Override
+ public boolean onPort(InetAddress remoteAddr, int remotePort) {
+ clearState();
+ this.remoteAddr = remoteAddr;
+ this.remotePort = remotePort;
+ return true;
+ }
+
+ @Override
+ public Socket onTransfer() {
+ if(server == null) {
+ // We're in PORT mode (not PASV)
+ if(remoteAddr == null || remotePort == 0) {
+ myLog.l(Log.INFO, "PORT mode but not initialized correctly");
+ clearState();
+ return null;
+ }
+ Socket socket;
+ try {
+ socket = new Socket(remoteAddr, remotePort);
+ } catch (IOException e) {
+ myLog.l(Log.INFO,
+ "Couldn't open PORT data socket to: " +
+ remoteAddr.toString() + ":" + remotePort);
+ clearState();
+ return null;
+ }
+
+ // Kill the socket if nothing happens for X milliseconds
+ try {
+ socket.setSoTimeout(Defaults.SO_TIMEOUT_MS);
+ } catch (Exception e) {
+ myLog.l(Log.ERROR, "Couldn't set SO_TIMEOUT");
+ clearState();
+ return null;
+ }
+
+ return socket;
+ } else {
+ // We're in PASV mode (not PORT)
+ Socket socket = null;
+ try {
+ socket = server.accept();
+ myLog.l(Log.DEBUG, "onTransfer pasv accept successful");
+ } catch (Exception e) {
+ myLog.l(Log.INFO, "Exception accepting PASV socket");
+ socket = null;
+ }
+ clearState();
+ return socket; // will be null if error occurred
+ }
+ }
+
+ /**
+ * Return the port number that the remote client should be informed of (in the body
+ * of the PASV response).
+ * @return The port number, or -1 if error.
+ */
+ public int getPortNumber() {
+ if(server != null) {
+ return server.getLocalPort(); // returns -1 if serversocket is unbound
+ } else {
+ return -1;
+ }
+ }
+
+ @Override
+ public InetAddress getPasvIp() {
+ //String retVal = server.getInetAddress().getHostAddress();
+ return FTPServerService.getWifiIp();
+ }
+
+ @Override
+ public void reportTraffic(long bytes) {
+ // ignore, we don't care about how much traffic goes over wifi.
+ }
+}
diff --git a/src/main/java/org/swiftp/server/ProxyConnector.java b/src/main/java/org/swiftp/server/ProxyConnector.java
index 962d094163f5494f2e1b862c4883f7411fb3e7ca..e0f264bab0ac01ce14fc3d31d5bed9b0a8da0c9f 100644
--- a/src/main/java/org/swiftp/server/ProxyConnector.java
+++ b/src/main/java/org/swiftp/server/ProxyConnector.java
@@ -1,769 +1,769 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
- */
-
-package org.swiftp.server;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.swiftp.Defaults;
-import org.swiftp.FTPServerService;
-import org.swiftp.Globals;
-import org.swiftp.MyLog;
-import org.swiftp.R;
-import org.swiftp.Util;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.util.Log;
-
-public class ProxyConnector extends Thread {
- public static final int IN_BUF_SIZE = 2048;
- public static final String ENCODING = "UTF-8";
- public static final int RESPONSE_WAIT_MS = 10000;
- public static final int QUEUE_WAIT_MS = 20000;
- public static final long UPDATE_USAGE_BYTES = 5000000;
- public static final String PREFERRED_SERVER = "preferred_server"; // preferences
- public static final int CONNECT_TIMEOUT = 5000;
-
- private final FTPServerService ftpServerService;
- private final MyLog myLog = new MyLog(getClass().getName());
- private JSONObject response = null;
- private Thread responseWaiter = null;
- private final Queue queuedRequestThreads = new LinkedList();
- private Socket commandSocket = null;
- private OutputStream out = null;
- private String hostname = null;
- private InputStream inputStream = null;
- private long proxyUsage = 0;
- private State proxyState = State.DISCONNECTED;
- private String prefix;
- private String proxyMessage = null;
-
- public enum State {
- CONNECTING, CONNECTED, FAILED, UNREACHABLE, DISCONNECTED
- };
-
- // QuotaStats cachedQuotaStats = null; // quotas have been canceled for now
-
- static final String USAGE_PREFS_NAME = "proxy_usage_data";
-
- /*
- * We establish a so-called "command session" to the proxy. New connections will be
- * handled by creating addition control and data connections to the proxy. See
- * proxy_protocol.txt and proxy_architecture.pdf for an explanation of how proxying
- * works. Hint: it's complicated.
- */
-
- public ProxyConnector(FTPServerService ftpServerService) {
- this.ftpServerService = ftpServerService;
- this.proxyUsage = getPersistedProxyUsage();
- setProxyState(State.DISCONNECTED);
- Globals.setProxyConnector(this);
- }
-
- @Override
- public void run() {
- myLog.i("In ProxyConnector.run()");
- setProxyState(State.CONNECTING);
- try {
- String candidateProxies[] = getProxyList();
- for (String candidateHostname : candidateProxies) {
- hostname = candidateHostname;
- commandSocket = newAuthedSocket(hostname, Defaults.REMOTE_PROXY_PORT);
- if (commandSocket == null) {
- continue;
- }
- commandSocket.setSoTimeout(0); // 0 == forever
- // commandSocket.setKeepAlive(true);
- // Now that we have authenticated, we want to start the command session so
- // we can
- // be notified of pending control sessions.
- JSONObject request = makeJsonRequest("start_command_session");
- response = sendRequest(commandSocket, request);
- if (response == null) {
- myLog.i("Couldn't create proxy command session");
- continue; // try next server
- }
- if (!response.has("prefix")) {
- myLog.l(Log.INFO,
- "start_command_session didn't receive a prefix in response");
- continue; // try next server
- }
- prefix = response.getString("prefix");
- response = null; // Indicate that response is free for other use
- myLog.l(Log.INFO, "Got prefix of: " + prefix);
- break; // breaking with commandSocket != null indicates success
- }
- if (commandSocket == null) {
- myLog.l(Log.INFO, "No proxies accepted connection, failing.");
- setProxyState(State.UNREACHABLE);
- return;
- }
- setProxyState(State.CONNECTED);
- preferServer(hostname);
- inputStream = commandSocket.getInputStream();
- out = commandSocket.getOutputStream();
- int numBytes;
- byte[] bytes = new byte[IN_BUF_SIZE];
- // spawnQuotaRequester().start();
- while (true) {
- myLog.d("to proxy read()");
- numBytes = inputStream.read(bytes);
- incrementProxyUsage(numBytes);
- myLog.d("from proxy read()");
- JSONObject incomingJson = null;
- if (numBytes> 0) {
- String responseString = new String(bytes, ENCODING);
- incomingJson = new JSONObject(responseString);
- if (incomingJson.has("action")) {
- // If the incoming JSON object has an "action" field, then it is a
- // request, and not a response
- incomingCommand(incomingJson);
- } else {
- // If the incoming JSON object does not have an "action" field,
- // then
- // it is a response to a request we sent earlier.
- // If there's an object waiting for a response, then that object
- // will be referenced by responseWaiter.
- if (responseWaiter != null) {
- if (response != null) {
- myLog.l(Log.INFO,
- "Overwriting existing cmd session response");
- }
- response = incomingJson;
- responseWaiter.interrupt();
- } else {
- myLog.l(Log.INFO, "Response received but no responseWaiter");
- }
- }
- } else if (numBytes == 0) {
- myLog.d("Command socket read 0 bytes, looping");
- } else { // numBytes < 0 - myLog.l(Log.DEBUG, "Command socket end of stream, exiting"); - if (proxyState != State.DISCONNECTED) { - // Set state to FAILED unless this was an intentional - // socket closure. - setProxyState(State.FAILED); - } - break; - } - } - myLog.l(Log.INFO, "ProxyConnector thread quitting cleanly"); - } catch (IOException e) { - myLog.l(Log.INFO, "IOException in command session: " + e); - setProxyState(State.FAILED); - } catch (JSONException e) { - myLog.l(Log.INFO, "Commmand socket JSONException: " + e); - setProxyState(State.FAILED); - } catch (Exception e) { - myLog.l(Log.INFO, "Other exception in ProxyConnector: " + e); - setProxyState(State.FAILED); - } finally { - Globals.setProxyConnector(null); - hostname = null; - myLog.d("ProxyConnector.run() returning"); - persistProxyUsage(); - } - } - - // This function is used to spawn a new Thread that will make a request over the - // command thread. Since the main ProxyConnector thread handles the input - // request/response de-multiplexing, it cannot also make a request using the - // sendCmdSocketRequest, since sendCmdSocketRequest will block waiting for - // a response, but the same thread is expected to deliver the response. - // The short story is, if the main ProxyConnector command session thread wants to - // make a request, the easiest way is to spawn a new thread and have it call - // sendCmdSocketRequest in the same way as any other thread. - // private Thread spawnQuotaRequester() { - // return new Thread() { - // public void run() { - // getQuotaStats(false); - // } - // }; - // } - - /** - * Since we want devices to generally stick with the same proxy server, and we may - * want to explicitly redirect some devices to other servers, we have this mechanism - * to store a "preferred server" on the device. - */ - private void preferServer(String hostname) { - SharedPreferences prefs = Globals.getContext().getSharedPreferences( - PREFERRED_SERVER, 0); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(PREFERRED_SERVER, hostname); - editor.commit(); - } - - private String[] getProxyList() { - SharedPreferences prefs = Globals.getContext().getSharedPreferences( - PREFERRED_SERVER, 0); - String preferred = prefs.getString(PREFERRED_SERVER, null); - - String[] allProxies; - - if (Defaults.release) { - allProxies = new String[] { "c1.swiftp.org", "c2.swiftp.org", - "c3.swiftp.org", "c4.swiftp.org", "c5.swiftp.org", "c6.swiftp.org", - "c7.swiftp.org", "c8.swiftp.org", "c9.swiftp.org" }; - } else { - // allProxies = new String[] { - // "cdev.swiftp.org" - // }; - allProxies = new String[] { "c1.swiftp.org", "c2.swiftp.org", - "c3.swiftp.org", "c4.swiftp.org", "c5.swiftp.org", "c6.swiftp.org", - "c7.swiftp.org", "c8.swiftp.org", "c9.swiftp.org" }; - } - - // We should randomly permute the server list in order to spread - // load between servers. Collections offers a shuffle() function - // that does this, so we'll convert to List and back to String[]. - List proxyList = Arrays.asList(allProxies);
- Collections.shuffle(proxyList);
- allProxies = proxyList.toArray(new String[] {}); // arg used for type
-
- // Return preferred server first, followed by all others
- if (preferred == null) {
- return allProxies;
- } else {
- return Util.concatStrArrays(new String[] { preferred }, allProxies);
- }
- }
-
- private boolean checkAndPrintJsonError(JSONObject json) throws JSONException {
- if (json.has("error_code")) {
- // The returned JSON object will have a field called "errorCode"
- // if there was a problem executing our request.
- StringBuilder s = new StringBuilder("Error in JSON response, code: ");
- s.append(json.getString("error_code"));
- if (json.has("error_string")) {
- s.append(", string: ");
- s.append(json.getString("error_string"));
- }
- myLog.l(Log.INFO, s.toString());
-
- // Obsolete: there's no authentication anymore
- // Dev code to enable frequent database wipes. If we fail to login,
- // remove our stored account info, causing a create_account action
- // next time.
- // if(!Defaults.release) {
- // if(json.getInt("error_code") == 11) {
- // myLog.l(Log.DEBUG, "Dev: removing secret due to login failure");
- // removeSecret();
- // }
- // }
- return true;
- }
- return false;
- }
-
- /**
- * Reads our persistent storage, looking for a stored proxy authentication secret.
- *
- * @return The secret, if present, or null.
- */
- // Obsolete, there's no authentication anymore
- /*
- * private String retrieveSecret() { SharedPreferences settings =
- * Globals.getContext(). getSharedPreferences(Defaults.getSettingsName(),
- * Defaults.getSettingsMode()); return settings.getString("proxySecret", null); }
- */
-
- // Obsolete, there's no authentication anymore
- /*
- * private void storeSecret(String secret) { SharedPreferences settings =
- * Globals.getContext(). getSharedPreferences(Defaults.getSettingsName(),
- * Defaults.getSettingsMode()); Editor editor = settings.edit();
- * editor.putString("proxySecret", secret); editor.commit(); }
- */
-
- // Obsolete, there's no authentication anymore
- /*
- * private void removeSecret() { SharedPreferences settings = Globals.getContext().
- * getSharedPreferences(Defaults.getSettingsName(), Defaults.getSettingsMode());
- * Editor editor = settings.edit(); editor.remove("proxySecret"); editor.commit(); }
- */
-
- private void incomingCommand(JSONObject json) {
- try {
- String action = json.getString("action");
- if (action.equals("control_connection_waiting")) {
- startControlSession(json.getInt("port"));
- } else if (action.equals("prefer_server")) {
- String host = json.getString("host"); // throws JSONException, fine
- preferServer(host);
- myLog.i("New preferred server: " + host);
- } else if (action.equals("message")) {
- proxyMessage = json.getString("text");
- myLog.i("Got news from proxy server: \"" + proxyMessage + "\"");
- // TODO: send intent to notify UI about news
- // FTPServerService.updateClients(); // UI update to show message
- } else if (action.equals("noop")) {
- myLog.d("Proxy noop");
- } else {
- myLog.l(Log.INFO, "Unsupported incoming action: " + action);
- }
- // If we're starting a control session register with ftpServerService
- } catch (JSONException e) {
- myLog.l(Log.INFO, "JSONException in proxy incomingCommand");
- }
- }
-
- private void startControlSession(int port) {
- Socket socket;
- myLog.d("Starting new proxy FTP control session");
- socket = newAuthedSocket(hostname, port);
- if (socket == null) {
- myLog.i("startControlSession got null authed socket");
- return;
- }
- ProxyDataSocketFactory dataSocketFactory = new ProxyDataSocketFactory();
- SessionThread thread = new SessionThread(socket, dataSocketFactory,
- SessionThread.Source.PROXY);
- thread.start();
- ftpServerService.registerSessionThread(thread);
- }
-
- /**
- * Connects an outgoing socket to the proxy and authenticates, creating an account if
- * necessary.
- */
- private Socket newAuthedSocket(String hostname, int port) {
- if (hostname == null) {
- myLog.i("newAuthedSocket can't connect to null host");
- return null;
- }
- JSONObject json = new JSONObject();
- // String secret = retrieveSecret();
- Socket socket;
- OutputStream out = null;
- InputStream in = null;
-
- try {
- myLog.d("Opening proxy connection to " + hostname + ":" + port);
- socket = new Socket();
- socket.connect(new InetSocketAddress(hostname, port), CONNECT_TIMEOUT);
- json.put("android_id", Util.getAndroidId());
- json.put("swiftp_version", Util.getVersion());
- json.put("action", "login");
- out = socket.getOutputStream();
- in = socket.getInputStream();
- int numBytes;
-
- out.write(json.toString().getBytes(ENCODING));
- myLog.l(Log.DEBUG, "Sent login request");
- // Read and parse the server's response
- byte[] bytes = new byte[IN_BUF_SIZE];
- // Here we assume that the server's response will all be contained in
- // a single read, which may be unsafe for large responses
- numBytes = in.read(bytes);
- if (numBytes == -1) {
- myLog.l(Log.INFO, "Proxy socket closed while waiting for auth response");
- return null;
- } else if (numBytes == 0) {
- myLog.l(Log.INFO, "Short network read waiting for auth, quitting");
- return null;
- }
- json = new JSONObject(new String(bytes, 0, numBytes, ENCODING));
- if (checkAndPrintJsonError(json)) {
- return null;
- }
- myLog.d("newAuthedSocket successful");
- return socket;
- } catch (Exception e) {
- myLog.i("Exception during proxy connection or authentication: " + e);
- return null;
- }
- }
-
- public void quit() {
- setProxyState(State.DISCONNECTED);
- try {
- sendRequest(commandSocket, makeJsonRequest("finished")); // ignore reply
-
- if (inputStream != null) {
- myLog.d("quit() closing proxy inputStream");
- inputStream.close();
- } else {
- myLog.d("quit() won't close null inputStream");
- }
- if (commandSocket != null) {
- myLog.d("quit() closing proxy socket");
- commandSocket.close();
- } else {
- myLog.d("quit() won't close null socket");
- }
- } catch (IOException e) {
- } catch (JSONException e) {
- }
- persistProxyUsage();
- Globals.setProxyConnector(null);
- }
-
- @SuppressWarnings("unused")
- private JSONObject sendCmdSocketRequest(JSONObject json) {
- try {
- boolean queued;
- synchronized (this) {
- if (responseWaiter == null) {
- responseWaiter = Thread.currentThread();
- queued = false;
- myLog.d("sendCmdSocketRequest proceeding without queue");
- } else if (!responseWaiter.isAlive()) {
- // This code should never run. It is meant to recover from a situation
- // where there is a thread that sent a proxy request but died before
- // starting the subsequent request. If this is the case, the correct
- // behavior is to run the next queued thread in the queue, or if the
- // queue is empty, to perform our own request.
- myLog.l(Log.INFO, "Won't wait on dead responseWaiter");
- if (queuedRequestThreads.size() == 0) {
- responseWaiter = Thread.currentThread();
- queued = false;
- } else {
- queuedRequestThreads.add(Thread.currentThread());
- queuedRequestThreads.remove().interrupt(); // start queued thread
- queued = true;
- }
- } else {
- myLog.d("sendCmdSocketRequest queueing thread");
- queuedRequestThreads.add(Thread.currentThread());
- queued = true;
- }
- }
- // If a different thread has sent a request and is waiting for a response,
- // then the current thread will be in a queue waiting for an interrupt
- if (queued) {
- // The current thread must wait until we are popped off the waiting queue
- // and receive an interrupt()
- boolean interrupted = false;
- try {
- myLog.d("Queued cmd session request thread sleeping...");
- Thread.sleep(QUEUE_WAIT_MS);
- } catch (InterruptedException e) {
- myLog.l(Log.DEBUG, "Proxy request popped and ready");
- interrupted = true;
- }
- if (!interrupted) {
- myLog.l(Log.INFO, "Timed out waiting on proxy queue");
- return null;
- }
- }
- // We have been popped from the wait queue if necessary, and now it's time
- // to send the request.
- try {
- responseWaiter = Thread.currentThread();
- byte[] outboundData = Util.jsonToByteArray(json);
- try {
- out.write(outboundData);
- } catch (IOException e) {
- myLog.l(Log.INFO, "IOException sending proxy request");
- return null;
- }
- // Wait RESPONSE_WAIT_MS for a response from the proxy
- boolean interrupted = false;
- try {
- // Wait for the main ProxyConnector thread to interrupt us, meaning
- // that a response has been received.
- myLog.d("Cmd session request sleeping until response");
- Thread.sleep(RESPONSE_WAIT_MS);
- } catch (InterruptedException e) {
- myLog.d("Cmd session response received");
- interrupted = true;
- }
- if (!interrupted) {
- myLog.l(Log.INFO, "Proxy request timed out");
- return null;
- }
- // At this point, the main ProxyConnector thread will have stored
- // our response in "JSONObject response".
- myLog.d("Cmd session response was: " + response);
- return response;
- } finally {
- // Make sure that when this request finishes, the next thread on the
- // queue gets started.
- synchronized (this) {
- if (queuedRequestThreads.size() != 0) {
- queuedRequestThreads.remove().interrupt();
- }
- }
- }
- } catch (JSONException e) {
- myLog.l(Log.INFO, "JSONException in sendRequest: " + e);
- return null;
- }
- }
-
- public JSONObject sendRequest(InputStream in, OutputStream out, JSONObject request)
- throws JSONException {
- try {
- out.write(Util.jsonToByteArray(request));
- byte[] bytes = new byte[IN_BUF_SIZE];
- int numBytes = in.read(bytes);
- if (numBytes < 1) { - myLog.i("Proxy sendRequest short read on response"); - return null; - } - JSONObject response = Util.byteArrayToJson(bytes); - if (response == null) { - myLog.i("Null response to sendRequest"); - } - if (checkAndPrintJsonError(response)) { - myLog.i("Error response to sendRequest"); - return null; - } - return response; - } catch (IOException e) { - myLog.i("IOException in proxy sendRequest: " + e); - return null; - } - } - - public JSONObject sendRequest(Socket socket, JSONObject request) throws JSONException { - try { - if (socket == null) { - // The server is probably shutting down - myLog.i("null socket in ProxyConnector.sendRequest()"); - return null; - } else { - return sendRequest(socket.getInputStream(), socket.getOutputStream(), - request); - } - } catch (IOException e) { - myLog.i("IOException in proxy sendRequest wrapper: " + e); - return null; - } - } - - public ProxyDataSocketInfo pasvListen() { - try { - // connect to proxy and authenticate - myLog.d("Sending data_pasv_listen to proxy"); - Socket socket = newAuthedSocket(this.hostname, Defaults.REMOTE_PROXY_PORT); - if (socket == null) { - myLog.i("pasvListen got null socket"); - return null; - } - JSONObject request = makeJsonRequest("data_pasv_listen"); - - JSONObject response = sendRequest(socket, request); - if (response == null) { - return null; - } - int port = response.getInt("port"); - return new ProxyDataSocketInfo(socket, port); - } catch (JSONException e) { - myLog.l(Log.INFO, "JSONException in pasvListen"); - return null; - } - } - - public Socket dataPortConnect(InetAddress clientAddr, int clientPort) { - /** - * This function is called by a ProxyDataSocketFactory when it's time to transfer - * some data in PORT mode (not PASV mode). We send a data_port_connect request to - * the proxy, containing the IP and port of the FTP client to which a connection - * should be made. - */ - try { - myLog.d("Sending data_port_connect to proxy"); - Socket socket = newAuthedSocket(this.hostname, Defaults.REMOTE_PROXY_PORT); - if (socket == null) { - myLog.i("dataPortConnect got null socket"); - return null; - } - JSONObject request = makeJsonRequest("data_port_connect"); - request.put("address", clientAddr.getHostAddress()); - request.put("port", clientPort); - JSONObject response = sendRequest(socket, request); - if (response == null) { - return null; // logged elsewhere - } - return socket; - } catch (JSONException e) { - myLog.i("JSONException in dataPortConnect"); - return null; - } - } - - /** - * Given a socket returned from pasvListen(), send a data_pasv_accept request over the - * socket to the proxy, which should result in a socket that is ready for data - * transfer with the FTP client. Of course, this will only work if the FTP client - * connects to the proxy like it's supposed to. The client will have already been told - * to connect by the response to its PASV command. - * - * This should only be called from the onTransfer method of ProxyDataSocketFactory. - * - * @param socket - * A socket previously returned from ProxyConnector.pasvListen() - * @return true if the accept operation completed OK, otherwise false - */ - - public boolean pasvAccept(Socket socket) { - try { - JSONObject request = makeJsonRequest("data_pasv_accept"); - JSONObject response = sendRequest(socket, request); - if (response == null) { - return false; // error is logged elsewhere - } - if (checkAndPrintJsonError(response)) { - myLog.i("Error response to data_pasv_accept"); - return false; - } - // The proxy's response will be an empty JSON object on success - myLog.d("Proxy data_pasv_accept successful"); - return true; - } catch (JSONException e) { - myLog.i("JSONException in pasvAccept: " + e); - return false; - } - } - - public InetAddress getProxyIp() { - if (this.isAlive()) { - if (commandSocket.isConnected()) { - return commandSocket.getInetAddress(); - } - } - return null; - } - - private JSONObject makeJsonRequest(String action) throws JSONException { - JSONObject json = new JSONObject(); - json.put("action", action); - return json; - } - - /* - * Quotas have been canceled for now public QuotaStats getQuotaStats(boolean - * canUseCached) { if(canUseCached) { if(cachedQuotaStats != null) { - * myLog.d("Returning cachedQuotaStats"); return cachedQuotaStats; } else { - * myLog.d("Would return cached quota stats but none retrieved"); } } // If there's no - * cached quota stats, or if the called wants fresh stats, // make a JSON request to - * the proxy, assuming the command session is open. try { JSONObject response = - * sendCmdSocketRequest(makeJsonRequest("check_quota")); int used, quota; if(response - * == null) { myLog.w("check_quota got null response"); return null; } used = - * response.getInt("used"); quota = response.getInt("quota"); - * myLog.d("Got quota response of " + used + "/" + quota); cachedQuotaStats = new - * QuotaStats(used, quota) ; return cachedQuotaStats; } catch (JSONException e) { - * myLog.w("JSONException in getQuota: " + e); return null; } } - */ - - // We want to track the total amount of data sent via the proxy server, to - // show it to the user and encourage them to donate. - void persistProxyUsage() { - if (proxyUsage == 0) { - return; // This shouldn't happen, but just for safety - } - SharedPreferences prefs = Globals.getContext().getSharedPreferences( - USAGE_PREFS_NAME, 0); // 0 == private - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(USAGE_PREFS_NAME, proxyUsage); - editor.commit(); - myLog.d("Persisted proxy usage to preferences"); - } - - long getPersistedProxyUsage() { - // This gets the last persisted value for bytes transferred through - // the proxy. It can be out of date since it doesn't include data - // transferred during the current session. - SharedPreferences prefs = Globals.getContext().getSharedPreferences( - USAGE_PREFS_NAME, 0); // 0 == private - return prefs.getLong(USAGE_PREFS_NAME, 0); // Default count of 0 - } - - long getProxyUsage() { - // This gets the running total of all proxy usage, which may not have - // been persisted yet. - return proxyUsage; - } - - void incrementProxyUsage(long num) { - long oldProxyUsage = proxyUsage; - proxyUsage += num; - if (proxyUsage % UPDATE_USAGE_BYTES < oldProxyUsage % UPDATE_USAGE_BYTES) { - // TODO: Use intent to update UI - // FTPServerService.updateClients(); - persistProxyUsage(); - } - } - - public State getProxyState() { - return proxyState; - } - - private void setProxyState(State state) { - proxyState = state; - myLog.l(Log.DEBUG, "Proxy state changed to " + state, true); - // TODO: Use intent to update UI - // FTPServerService.updateClients(); - } - - static public String stateToString(State s) { - Context ctx = Globals.getContext(); - switch (s) { - case DISCONNECTED: - return ctx.getString(R.string.pst_disconnected); - case CONNECTING: - return ctx.getString(R.string.pst_connecting); - case CONNECTED: - return ctx.getString(R.string.pst_connected); - case FAILED: - return ctx.getString(R.string.pst_failed); - case UNREACHABLE: - return ctx.getString(R.string.pst_unreachable); - default: - return ctx.getString(R.string.unknown); - } - } - - /** - * The URL to which users should point their FTP client. - */ - public String getURL() { - if (proxyState == State.CONNECTED) { - String username = Globals.getUsername(); - if (username != null) { - return "ftp://" + prefix + "_" + username + "@" + hostname; - } - } - return Globals.getContext().getString(R.string.unknown); - } - - /** - * If the proxy sends a human-readable message, it can be retrieved by calling this - * function. Returns null if no message has been received. - */ - public String getProxyMessage() { - return proxyMessage; - } - -} +/* +Copyright 2009 David Revell + +This file is part of SwiFTP. + +SwiFTP is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +SwiFTP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with SwiFTP. If not, see .
+ */
+
+package org.swiftp.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.swiftp.Defaults;
+import org.swiftp.FTPServerService;
+import org.swiftp.Globals;
+import org.swiftp.MyLog;
+import org.swiftp.R;
+import org.swiftp.Util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public class ProxyConnector extends Thread {
+ public static final int IN_BUF_SIZE = 2048;
+ public static final String ENCODING = "UTF-8";
+ public static final int RESPONSE_WAIT_MS = 10000;
+ public static final int QUEUE_WAIT_MS = 20000;
+ public static final long UPDATE_USAGE_BYTES = 5000000;
+ public static final String PREFERRED_SERVER = "preferred_server"; // preferences
+ public static final int CONNECT_TIMEOUT = 5000;
+
+ private final FTPServerService ftpServerService;
+ private final MyLog myLog = new MyLog(getClass().getName());
+ private JSONObject response = null;
+ private Thread responseWaiter = null;
+ private final Queue queuedRequestThreads = new LinkedList();
+ private Socket commandSocket = null;
+ private OutputStream out = null;
+ private String hostname = null;
+ private InputStream inputStream = null;
+ private long proxyUsage = 0;
+ private State proxyState = State.DISCONNECTED;
+ private String prefix;
+ private String proxyMessage = null;
+
+ public enum State {
+ CONNECTING, CONNECTED, FAILED, UNREACHABLE, DISCONNECTED
+ };
+
+ // QuotaStats cachedQuotaStats = null; // quotas have been canceled for now
+
+ static final String USAGE_PREFS_NAME = "proxy_usage_data";
+
+ /*
+ * We establish a so-called "command session" to the proxy. New connections will be
+ * handled by creating addition control and data connections to the proxy. See
+ * proxy_protocol.txt and proxy_architecture.pdf for an explanation of how proxying
+ * works. Hint: it's complicated.
+ */
+
+ public ProxyConnector(FTPServerService ftpServerService) {
+ this.ftpServerService = ftpServerService;
+ this.proxyUsage = getPersistedProxyUsage();
+ setProxyState(State.DISCONNECTED);
+ Globals.setProxyConnector(this);
+ }
+
+ @Override
+ public void run() {
+ myLog.i("In ProxyConnector.run()");
+ setProxyState(State.CONNECTING);
+ try {
+ String candidateProxies[] = getProxyList();
+ for (String candidateHostname : candidateProxies) {
+ hostname = candidateHostname;
+ commandSocket = newAuthedSocket(hostname, Defaults.REMOTE_PROXY_PORT);
+ if (commandSocket == null) {
+ continue;
+ }
+ commandSocket.setSoTimeout(0); // 0 == forever
+ // commandSocket.setKeepAlive(true);
+ // Now that we have authenticated, we want to start the command session so
+ // we can
+ // be notified of pending control sessions.
+ JSONObject request = makeJsonRequest("start_command_session");
+ response = sendRequest(commandSocket, request);
+ if (response == null) {
+ myLog.i("Couldn't create proxy command session");
+ continue; // try next server
+ }
+ if (!response.has("prefix")) {
+ myLog.l(Log.INFO,
+ "start_command_session didn't receive a prefix in response");
+ continue; // try next server
+ }
+ prefix = response.getString("prefix");
+ response = null; // Indicate that response is free for other use
+ myLog.l(Log.INFO, "Got prefix of: " + prefix);
+ break; // breaking with commandSocket != null indicates success
+ }
+ if (commandSocket == null) {
+ myLog.l(Log.INFO, "No proxies accepted connection, failing.");
+ setProxyState(State.UNREACHABLE);
+ return;
+ }
+ setProxyState(State.CONNECTED);
+ preferServer(hostname);
+ inputStream = commandSocket.getInputStream();
+ out = commandSocket.getOutputStream();
+ int numBytes;
+ byte[] bytes = new byte[IN_BUF_SIZE];
+ // spawnQuotaRequester().start();
+ while (true) {
+ myLog.d("to proxy read()");
+ numBytes = inputStream.read(bytes);
+ incrementProxyUsage(numBytes);
+ myLog.d("from proxy read()");
+ JSONObject incomingJson = null;
+ if (numBytes> 0) {
+ String responseString = new String(bytes, ENCODING);
+ incomingJson = new JSONObject(responseString);
+ if (incomingJson.has("action")) {
+ // If the incoming JSON object has an "action" field, then it is a
+ // request, and not a response
+ incomingCommand(incomingJson);
+ } else {
+ // If the incoming JSON object does not have an "action" field,
+ // then
+ // it is a response to a request we sent earlier.
+ // If there's an object waiting for a response, then that object
+ // will be referenced by responseWaiter.
+ if (responseWaiter != null) {
+ if (response != null) {
+ myLog.l(Log.INFO,
+ "Overwriting existing cmd session response");
+ }
+ response = incomingJson;
+ responseWaiter.interrupt();
+ } else {
+ myLog.l(Log.INFO, "Response received but no responseWaiter");
+ }
+ }
+ } else if (numBytes == 0) {
+ myLog.d("Command socket read 0 bytes, looping");
+ } else { // numBytes < 0 + myLog.l(Log.DEBUG, "Command socket end of stream, exiting"); + if (proxyState != State.DISCONNECTED) { + // Set state to FAILED unless this was an intentional + // socket closure. + setProxyState(State.FAILED); + } + break; + } + } + myLog.l(Log.INFO, "ProxyConnector thread quitting cleanly"); + } catch (IOException e) { + myLog.l(Log.INFO, "IOException in command session: " + e); + setProxyState(State.FAILED); + } catch (JSONException e) { + myLog.l(Log.INFO, "Commmand socket JSONException: " + e); + setProxyState(State.FAILED); + } catch (Exception e) { + myLog.l(Log.INFO, "Other exception in ProxyConnector: " + e); + setProxyState(State.FAILED); + } finally { + Globals.setProxyConnector(null); + hostname = null; + myLog.d("ProxyConnector.run() returning"); + persistProxyUsage(); + } + } + + // This function is used to spawn a new Thread that will make a request over the + // command thread. Since the main ProxyConnector thread handles the input + // request/response de-multiplexing, it cannot also make a request using the + // sendCmdSocketRequest, since sendCmdSocketRequest will block waiting for + // a response, but the same thread is expected to deliver the response. + // The short story is, if the main ProxyConnector command session thread wants to + // make a request, the easiest way is to spawn a new thread and have it call + // sendCmdSocketRequest in the same way as any other thread. + // private Thread spawnQuotaRequester() { + // return new Thread() { + // public void run() { + // getQuotaStats(false); + // } + // }; + // } + + /** + * Since we want devices to generally stick with the same proxy server, and we may + * want to explicitly redirect some devices to other servers, we have this mechanism + * to store a "preferred server" on the device. + */ + private void preferServer(String hostname) { + SharedPreferences prefs = Globals.getContext().getSharedPreferences( + PREFERRED_SERVER, 0); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString(PREFERRED_SERVER, hostname); + editor.commit(); + } + + private String[] getProxyList() { + SharedPreferences prefs = Globals.getContext().getSharedPreferences( + PREFERRED_SERVER, 0); + String preferred = prefs.getString(PREFERRED_SERVER, null); + + String[] allProxies; + + if (Defaults.release) { + allProxies = new String[] { "c1.swiftp.org", "c2.swiftp.org", + "c3.swiftp.org", "c4.swiftp.org", "c5.swiftp.org", "c6.swiftp.org", + "c7.swiftp.org", "c8.swiftp.org", "c9.swiftp.org" }; + } else { + // allProxies = new String[] { + // "cdev.swiftp.org" + // }; + allProxies = new String[] { "c1.swiftp.org", "c2.swiftp.org", + "c3.swiftp.org", "c4.swiftp.org", "c5.swiftp.org", "c6.swiftp.org", + "c7.swiftp.org", "c8.swiftp.org", "c9.swiftp.org" }; + } + + // We should randomly permute the server list in order to spread + // load between servers. Collections offers a shuffle() function + // that does this, so we'll convert to List and back to String[]. + List proxyList = Arrays.asList(allProxies);
+ Collections.shuffle(proxyList);
+ allProxies = proxyList.toArray(new String[] {}); // arg used for type
+
+ // Return preferred server first, followed by all others
+ if (preferred == null) {
+ return allProxies;
+ } else {
+ return Util.concatStrArrays(new String[] { preferred }, allProxies);
+ }
+ }
+
+ private boolean checkAndPrintJsonError(JSONObject json) throws JSONException {
+ if (json.has("error_code")) {
+ // The returned JSON object will have a field called "errorCode"
+ // if there was a problem executing our request.
+ StringBuilder s = new StringBuilder("Error in JSON response, code: ");
+ s.append(json.getString("error_code"));
+ if (json.has("error_string")) {
+ s.append(", string: ");
+ s.append(json.getString("error_string"));
+ }
+ myLog.l(Log.INFO, s.toString());
+
+ // Obsolete: there's no authentication anymore
+ // Dev code to enable frequent database wipes. If we fail to login,
+ // remove our stored account info, causing a create_account action
+ // next time.
+ // if(!Defaults.release) {
+ // if(json.getInt("error_code") == 11) {
+ // myLog.l(Log.DEBUG, "Dev: removing secret due to login failure");
+ // removeSecret();
+ // }
+ // }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Reads our persistent storage, looking for a stored proxy authentication secret.
+ *
+ * @return The secret, if present, or null.
+ */
+ // Obsolete, there's no authentication anymore
+ /*
+ * private String retrieveSecret() { SharedPreferences settings =
+ * Globals.getContext(). getSharedPreferences(Defaults.getSettingsName(),
+ * Defaults.getSettingsMode()); return settings.getString("proxySecret", null); }
+ */
+
+ // Obsolete, there's no authentication anymore
+ /*
+ * private void storeSecret(String secret) { SharedPreferences settings =
+ * Globals.getContext(). getSharedPreferences(Defaults.getSettingsName(),
+ * Defaults.getSettingsMode()); Editor editor = settings.edit();
+ * editor.putString("proxySecret", secret); editor.commit(); }
+ */
+
+ // Obsolete, there's no authentication anymore
+ /*
+ * private void removeSecret() { SharedPreferences settings = Globals.getContext().
+ * getSharedPreferences(Defaults.getSettingsName(), Defaults.getSettingsMode());
+ * Editor editor = settings.edit(); editor.remove("proxySecret"); editor.commit(); }
+ */
+
+ private void incomingCommand(JSONObject json) {
+ try {
+ String action = json.getString("action");
+ if (action.equals("control_connection_waiting")) {
+ startControlSession(json.getInt("port"));
+ } else if (action.equals("prefer_server")) {
+ String host = json.getString("host"); // throws JSONException, fine
+ preferServer(host);
+ myLog.i("New preferred server: " + host);
+ } else if (action.equals("message")) {
+ proxyMessage = json.getString("text");
+ myLog.i("Got news from proxy server: \"" + proxyMessage + "\"");
+ // TODO: send intent to notify UI about news
+ // FTPServerService.updateClients(); // UI update to show message
+ } else if (action.equals("noop")) {
+ myLog.d("Proxy noop");
+ } else {
+ myLog.l(Log.INFO, "Unsupported incoming action: " + action);
+ }
+ // If we're starting a control session register with ftpServerService
+ } catch (JSONException e) {
+ myLog.l(Log.INFO, "JSONException in proxy incomingCommand");
+ }
+ }
+
+ private void startControlSession(int port) {
+ Socket socket;
+ myLog.d("Starting new proxy FTP control session");
+ socket = newAuthedSocket(hostname, port);
+ if (socket == null) {
+ myLog.i("startControlSession got null authed socket");
+ return;
+ }
+ ProxyDataSocketFactory dataSocketFactory = new ProxyDataSocketFactory();
+ SessionThread thread = new SessionThread(socket, dataSocketFactory,
+ SessionThread.Source.PROXY);
+ thread.start();
+ ftpServerService.registerSessionThread(thread);
+ }
+
+ /**
+ * Connects an outgoing socket to the proxy and authenticates, creating an account if
+ * necessary.
+ */
+ private Socket newAuthedSocket(String hostname, int port) {
+ if (hostname == null) {
+ myLog.i("newAuthedSocket can't connect to null host");
+ return null;
+ }
+ JSONObject json = new JSONObject();
+ // String secret = retrieveSecret();
+ Socket socket;
+ OutputStream out = null;
+ InputStream in = null;
+
+ try {
+ myLog.d("Opening proxy connection to " + hostname + ":" + port);
+ socket = new Socket();
+ socket.connect(new InetSocketAddress(hostname, port), CONNECT_TIMEOUT);
+ json.put("android_id", Util.getAndroidId());
+ json.put("swiftp_version", Util.getVersion());
+ json.put("action", "login");
+ out = socket.getOutputStream();
+ in = socket.getInputStream();
+ int numBytes;
+
+ out.write(json.toString().getBytes(ENCODING));
+ myLog.l(Log.DEBUG, "Sent login request");
+ // Read and parse the server's response
+ byte[] bytes = new byte[IN_BUF_SIZE];
+ // Here we assume that the server's response will all be contained in
+ // a single read, which may be unsafe for large responses
+ numBytes = in.read(bytes);
+ if (numBytes == -1) {
+ myLog.l(Log.INFO, "Proxy socket closed while waiting for auth response");
+ return null;
+ } else if (numBytes == 0) {
+ myLog.l(Log.INFO, "Short network read waiting for auth, quitting");
+ return null;
+ }
+ json = new JSONObject(new String(bytes, 0, numBytes, ENCODING));
+ if (checkAndPrintJsonError(json)) {
+ return null;
+ }
+ myLog.d("newAuthedSocket successful");
+ return socket;
+ } catch (Exception e) {
+ myLog.i("Exception during proxy connection or authentication: " + e);
+ return null;
+ }
+ }
+
+ public void quit() {
+ setProxyState(State.DISCONNECTED);
+ try {
+ sendRequest(commandSocket, makeJsonRequest("finished")); // ignore reply
+
+ if (inputStream != null) {
+ myLog.d("quit() closing proxy inputStream");
+ inputStream.close();
+ } else {
+ myLog.d("quit() won't close null inputStream");
+ }
+ if (commandSocket != null) {
+ myLog.d("quit() closing proxy socket");
+ commandSocket.close();
+ } else {
+ myLog.d("quit() won't close null socket");
+ }
+ } catch (IOException e) {
+ } catch (JSONException e) {
+ }
+ persistProxyUsage();
+ Globals.setProxyConnector(null);
+ }
+
+ @SuppressWarnings("unused")
+ private JSONObject sendCmdSocketRequest(JSONObject json) {
+ try {
+ boolean queued;
+ synchronized (this) {
+ if (responseWaiter == null) {
+ responseWaiter = Thread.currentThread();
+ queued = false;
+ myLog.d("sendCmdSocketRequest proceeding without queue");
+ } else if (!responseWaiter.isAlive()) {
+ // This code should never run. It is meant to recover from a situation
+ // where there is a thread that sent a proxy request but died before
+ // starting the subsequent request. If this is the case, the correct
+ // behavior is to run the next queued thread in the queue, or if the
+ // queue is empty, to perform our own request.
+ myLog.l(Log.INFO, "Won't wait on dead responseWaiter");
+ if (queuedRequestThreads.size() == 0) {
+ responseWaiter = Thread.currentThread();
+ queued = false;
+ } else {
+ queuedRequestThreads.add(Thread.currentThread());
+ queuedRequestThreads.remove().interrupt(); // start queued thread
+ queued = true;
+ }
+ } else {
+ myLog.d("sendCmdSocketRequest queueing thread");
+ queuedRequestThreads.add(Thread.currentThread());
+ queued = true;
+ }
+ }
+ // If a different thread has sent a request and is waiting for a response,
+ // then the current thread will be in a queue waiting for an interrupt
+ if (queued) {
+ // The current thread must wait until we are popped off the waiting queue
+ // and receive an interrupt()
+ boolean interrupted = false;
+ try {
+ myLog.d("Queued cmd session request thread sleeping...");
+ Thread.sleep(QUEUE_WAIT_MS);
+ } catch (InterruptedException e) {
+ myLog.l(Log.DEBUG, "Proxy request popped and ready");
+ interrupted = true;
+ }
+ if (!interrupted) {
+ myLog.l(Log.INFO, "Timed out waiting on proxy queue");
+ return null;
+ }
+ }
+ // We have been popped from the wait queue if necessary, and now it's time
+ // to send the request.
+ try {
+ responseWaiter = Thread.currentThread();
+ byte[] outboundData = Util.jsonToByteArray(json);
+ try {
+ out.write(outboundData);
+ } catch (IOException e) {
+ myLog.l(Log.INFO, "IOException sending proxy request");
+ return null;
+ }
+ // Wait RESPONSE_WAIT_MS for a response from the proxy
+ boolean interrupted = false;
+ try {
+ // Wait for the main ProxyConnector thread to interrupt us, meaning
+ // that a response has been received.
+ myLog.d("Cmd session request sleeping until response");
+ Thread.sleep(RESPONSE_WAIT_MS);
+ } catch (InterruptedException e) {
+ myLog.d("Cmd session response received");
+ interrupted = true;
+ }
+ if (!interrupted) {
+ myLog.l(Log.INFO, "Proxy request timed out");
+ return null;
+ }
+ // At this point, the main ProxyConnector thread will have stored
+ // our response in "JSONObject response".
+ myLog.d("Cmd session response was: " + response);
+ return response;
+ } finally {
+ // Make sure that when this request finishes, the next thread on the
+ // queue gets started.
+ synchronized (this) {
+ if (queuedRequestThreads.size() != 0) {
+ queuedRequestThreads.remove().interrupt();
+ }
+ }
+ }
+ } catch (JSONException e) {
+ myLog.l(Log.INFO, "JSONException in sendRequest: " + e);
+ return null;
+ }
+ }
+
+ public JSONObject sendRequest(InputStream in, OutputStream out, JSONObject request)
+ throws JSONException {
+ try {
+ out.write(Util.jsonToByteArray(request));
+ byte[] bytes = new byte[IN_BUF_SIZE];
+ int numBytes = in.read(bytes);
+ if (numBytes < 1) { + myLog.i("Proxy sendRequest short read on response"); + return null; + } + JSONObject response = Util.byteArrayToJson(bytes); + if (response == null) { + myLog.i("Null response to sendRequest"); + } + if (checkAndPrintJsonError(response)) { + myLog.i("Error response to sendRequest"); + return null; + } + return response; + } catch (IOException e) { + myLog.i("IOException in proxy sendRequest: " + e); + return null; + } + } + + public JSONObject sendRequest(Socket socket, JSONObject request) throws JSONException { + try { + if (socket == null) { + // The server is probably shutting down + myLog.i("null socket in ProxyConnector.sendRequest()"); + return null; + } else { + return sendRequest(socket.getInputStream(), socket.getOutputStream(), + request); + } + } catch (IOException e) { + myLog.i("IOException in proxy sendRequest wrapper: " + e); + return null; + } + } + + public ProxyDataSocketInfo pasvListen() { + try { + // connect to proxy and authenticate + myLog.d("Sending data_pasv_listen to proxy"); + Socket socket = newAuthedSocket(this.hostname, Defaults.REMOTE_PROXY_PORT); + if (socket == null) { + myLog.i("pasvListen got null socket"); + return null; + } + JSONObject request = makeJsonRequest("data_pasv_listen"); + + JSONObject response = sendRequest(socket, request); + if (response == null) { + return null; + } + int port = response.getInt("port"); + return new ProxyDataSocketInfo(socket, port); + } catch (JSONException e) { + myLog.l(Log.INFO, "JSONException in pasvListen"); + return null; + } + } + + public Socket dataPortConnect(InetAddress clientAddr, int clientPort) { + /** + * This function is called by a ProxyDataSocketFactory when it's time to transfer + * some data in PORT mode (not PASV mode). We send a data_port_connect request to + * the proxy, containing the IP and port of the FTP client to which a connection + * should be made. + */ + try { + myLog.d("Sending data_port_connect to proxy"); + Socket socket = newAuthedSocket(this.hostname, Defaults.REMOTE_PROXY_PORT); + if (socket == null) { + myLog.i("dataPortConnect got null socket"); + return null; + } + JSONObject request = makeJsonRequest("data_port_connect"); + request.put("address", clientAddr.getHostAddress()); + request.put("port", clientPort); + JSONObject response = sendRequest(socket, request); + if (response == null) { + return null; // logged elsewhere + } + return socket; + } catch (JSONException e) { + myLog.i("JSONException in dataPortConnect"); + return null; + } + } + + /** + * Given a socket returned from pasvListen(), send a data_pasv_accept request over the + * socket to the proxy, which should result in a socket that is ready for data + * transfer with the FTP client. Of course, this will only work if the FTP client + * connects to the proxy like it's supposed to. The client will have already been told + * to connect by the response to its PASV command. + * + * This should only be called from the onTransfer method of ProxyDataSocketFactory. + * + * @param socket + * A socket previously returned from ProxyConnector.pasvListen() + * @return true if the accept operation completed OK, otherwise false + */ + + public boolean pasvAccept(Socket socket) { + try { + JSONObject request = makeJsonRequest("data_pasv_accept"); + JSONObject response = sendRequest(socket, request); + if (response == null) { + return false; // error is logged elsewhere + } + if (checkAndPrintJsonError(response)) { + myLog.i("Error response to data_pasv_accept"); + return false; + } + // The proxy's response will be an empty JSON object on success + myLog.d("Proxy data_pasv_accept successful"); + return true; + } catch (JSONException e) { + myLog.i("JSONException in pasvAccept: " + e); + return false; + } + } + + public InetAddress getProxyIp() { + if (this.isAlive()) { + if (commandSocket.isConnected()) { + return commandSocket.getInetAddress(); + } + } + return null; + } + + private JSONObject makeJsonRequest(String action) throws JSONException { + JSONObject json = new JSONObject(); + json.put("action", action); + return json; + } + + /* + * Quotas have been canceled for now public QuotaStats getQuotaStats(boolean + * canUseCached) { if(canUseCached) { if(cachedQuotaStats != null) { + * myLog.d("Returning cachedQuotaStats"); return cachedQuotaStats; } else { + * myLog.d("Would return cached quota stats but none retrieved"); } } // If there's no + * cached quota stats, or if the called wants fresh stats, // make a JSON request to + * the proxy, assuming the command session is open. try { JSONObject response = + * sendCmdSocketRequest(makeJsonRequest("check_quota")); int used, quota; if(response + * == null) { myLog.w("check_quota got null response"); return null; } used = + * response.getInt("used"); quota = response.getInt("quota"); + * myLog.d("Got quota response of " + used + "/" + quota); cachedQuotaStats = new + * QuotaStats(used, quota) ; return cachedQuotaStats; } catch (JSONException e) { + * myLog.w("JSONException in getQuota: " + e); return null; } } + */ + + // We want to track the total amount of data sent via the proxy server, to + // show it to the user and encourage them to donate. + void persistProxyUsage() { + if (proxyUsage == 0) { + return; // This shouldn't happen, but just for safety + } + SharedPreferences prefs = Globals.getContext().getSharedPreferences( + USAGE_PREFS_NAME, 0); // 0 == private + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong(USAGE_PREFS_NAME, proxyUsage); + editor.commit(); + myLog.d("Persisted proxy usage to preferences"); + } + + long getPersistedProxyUsage() { + // This gets the last persisted value for bytes transferred through + // the proxy. It can be out of date since it doesn't include data + // transferred during the current session. + SharedPreferences prefs = Globals.getContext().getSharedPreferences( + USAGE_PREFS_NAME, 0); // 0 == private + return prefs.getLong(USAGE_PREFS_NAME, 0); // Default count of 0 + } + + long getProxyUsage() { + // This gets the running total of all proxy usage, which may not have + // been persisted yet. + return proxyUsage; + } + + void incrementProxyUsage(long num) { + long oldProxyUsage = proxyUsage; + proxyUsage += num; + if (proxyUsage % UPDATE_USAGE_BYTES < oldProxyUsage % UPDATE_USAGE_BYTES) { + // TODO: Use intent to update UI + // FTPServerService.updateClients(); + persistProxyUsage(); + } + } + + public State getProxyState() { + return proxyState; + } + + private void setProxyState(State state) { + proxyState = state; + myLog.l(Log.DEBUG, "Proxy state changed to " + state, true); + // TODO: Use intent to update UI + // FTPServerService.updateClients(); + } + + static public String stateToString(State s) { + Context ctx = Globals.getContext(); + switch (s) { + case DISCONNECTED: + return ctx.getString(R.string.pst_disconnected); + case CONNECTING: + return ctx.getString(R.string.pst_connecting); + case CONNECTED: + return ctx.getString(R.string.pst_connected); + case FAILED: + return ctx.getString(R.string.pst_failed); + case UNREACHABLE: + return ctx.getString(R.string.pst_unreachable); + default: + return ctx.getString(R.string.unknown); + } + } + + /** + * The URL to which users should point their FTP client. + */ + public String getURL() { + if (proxyState == State.CONNECTED) { + String username = Globals.getUsername(); + if (username != null) { + return "ftp://" + prefix + "_" + username + "@" + hostname; + } + } + return Globals.getContext().getString(R.string.unknown); + } + + /** + * If the proxy sends a human-readable message, it can be retrieved by calling this + * function. Returns null if no message has been received. + */ + public String getProxyMessage() { + return proxyMessage; + } + +} diff --git a/src/main/java/org/swiftp/server/ProxyDataSocketFactory.java b/src/main/java/org/swiftp/server/ProxyDataSocketFactory.java index 1d909a6220de37520f17333e6c6119e1d1193db3..3864580edc6031df23e7df0bbb6aafbc3065e9e1 100644 --- a/src/main/java/org/swiftp/server/ProxyDataSocketFactory.java +++ b/src/main/java/org/swiftp/server/ProxyDataSocketFactory.java @@ -1,163 +1,163 @@ -/* -Copyright 2009 David Revell - -This file is part of SwiFTP. - -SwiFTP is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -SwiFTP is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.net.InetAddress;
-import java.net.Socket;
-
-import org.swiftp.Globals;
-
-import android.util.Log;
-
-/**
- * @author david
- *
- */
-public class ProxyDataSocketFactory extends DataSocketFactory {
- /**
- * Implements data socket connections that go through our proxy server
- * out on the net. The proxy sits between the FTP client and us, the server.
- * We have to build in some coordination between the server and proxy in order
- * for data sockets to be handled properly.
- *
- * When we receive a "PASV" command from a client, we have to request that the
- * proxy server open a port, accept a connection, and proxy all data on that
- * socket between ourself and the FTP client.
- *
- * When we receive a PORT command, we store the client's connection info,
- * and when it's time to being transferring data, we request that the proxy
- * make a connection to the client's IP & port and then proxy all data between
- * ourself and the FTP client.
- */
-
- private Socket socket;
- private int proxyListenPort;
- ProxyConnector proxyConnector;
- InetAddress clientAddress;
- int clientPort;
-
- public ProxyDataSocketFactory() {
- clearState();
- }
-
- private void clearState() {
- if(socket != null) {
- try {
- socket.close();
- } catch (Exception e) {}
- }
- socket = null;
- proxyConnector = null;
- clientAddress = null;
- proxyListenPort = 0;
- clientPort = 0;
- }
-
- @Override
- public InetAddress getPasvIp() {
- ProxyConnector pc = Globals.getProxyConnector();
- if(pc == null) {
- return null;
- }
- return pc.getProxyIp();
- }
-
-// public int getPortNumber() {
-// if(socket == )
-// return 0;
-// }
-
- @Override
- public int onPasv() {
- clearState();
- proxyConnector = Globals.getProxyConnector();
- if(proxyConnector == null) {
- myLog.l(Log.INFO, "Unexpected null proxyConnector in onPasv");
- clearState();
- return 0;
- }
- ProxyDataSocketInfo info = proxyConnector.pasvListen();
- if(info == null) {
- myLog.l(Log.INFO, "Null ProxyDataSocketInfo");
- clearState();
- return 0;
- }
- socket = info.getSocket();
- proxyListenPort = info.getRemotePublicPort();
- return proxyListenPort;
- }
-
- @Override
- public boolean onPort(InetAddress dest, int port) {
- clearState();
- proxyConnector = Globals.getProxyConnector();
- this.clientAddress = dest;
- this.clientPort = port;
- myLog.d("ProxyDataSocketFactory client port settings stored");
- return true;
- }
-
- /**
- * When the it's time for the SessionThread to actually begin PASV
- * data transfer with the client, it will call this function to get
- * a valid socket. The socket will have been created earlier with
- * a call to onPasv(). The result of calling onTransfer() will be
- * to cause the proxy to accept the incoming connection from the FTP
- * client and start proxying back to us (the FTP server). The socket
- * can then be handed back to the SessionThread which can use it as
- * if it were directly connected to the client.
- */
- @Override
- public Socket onTransfer() {
- if(proxyConnector == null) {
- myLog.w("Unexpected null proxyConnector in onTransfer");
- return null;
- }
-
- if(socket == null) {
- // We are in PORT mode (not PASV mode)
- if(proxyConnector == null) {
- myLog.l(Log.INFO, "Unexpected null proxyConnector in onTransfer");
- return null;
- }
- // May return null, that's fine. ProxyConnector will log errors.
- socket = proxyConnector.dataPortConnect(clientAddress, clientPort);
- return socket;
- } else {
- // We are in PASV mode (not PORT mode)
- if(proxyConnector.pasvAccept(socket)) {
- return socket;
- } else {
- myLog.w("proxyConnector pasvAccept failed");
- return null;
- }
- }
- }
-
- @Override
- public void reportTraffic(long bytes) {
- ProxyConnector pc = Globals.getProxyConnector();
- if(pc == null) {
- myLog.d("Can't report traffic, null ProxyConnector");
- } else {
- pc.incrementProxyUsage(bytes);
- }
- }
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.swiftp.Globals;
+
+import android.util.Log;
+
+/**
+ * @author david
+ *
+ */
+public class ProxyDataSocketFactory extends DataSocketFactory {
+ /**
+ * Implements data socket connections that go through our proxy server
+ * out on the net. The proxy sits between the FTP client and us, the server.
+ * We have to build in some coordination between the server and proxy in order
+ * for data sockets to be handled properly.
+ *
+ * When we receive a "PASV" command from a client, we have to request that the
+ * proxy server open a port, accept a connection, and proxy all data on that
+ * socket between ourself and the FTP client.
+ *
+ * When we receive a PORT command, we store the client's connection info,
+ * and when it's time to being transferring data, we request that the proxy
+ * make a connection to the client's IP & port and then proxy all data between
+ * ourself and the FTP client.
+ */
+
+ private Socket socket;
+ private int proxyListenPort;
+ ProxyConnector proxyConnector;
+ InetAddress clientAddress;
+ int clientPort;
+
+ public ProxyDataSocketFactory() {
+ clearState();
+ }
+
+ private void clearState() {
+ if(socket != null) {
+ try {
+ socket.close();
+ } catch (Exception e) {}
+ }
+ socket = null;
+ proxyConnector = null;
+ clientAddress = null;
+ proxyListenPort = 0;
+ clientPort = 0;
+ }
+
+ @Override
+ public InetAddress getPasvIp() {
+ ProxyConnector pc = Globals.getProxyConnector();
+ if(pc == null) {
+ return null;
+ }
+ return pc.getProxyIp();
+ }
+
+// public int getPortNumber() {
+// if(socket == )
+// return 0;
+// }
+
+ @Override
+ public int onPasv() {
+ clearState();
+ proxyConnector = Globals.getProxyConnector();
+ if(proxyConnector == null) {
+ myLog.l(Log.INFO, "Unexpected null proxyConnector in onPasv");
+ clearState();
+ return 0;
+ }
+ ProxyDataSocketInfo info = proxyConnector.pasvListen();
+ if(info == null) {
+ myLog.l(Log.INFO, "Null ProxyDataSocketInfo");
+ clearState();
+ return 0;
+ }
+ socket = info.getSocket();
+ proxyListenPort = info.getRemotePublicPort();
+ return proxyListenPort;
+ }
+
+ @Override
+ public boolean onPort(InetAddress dest, int port) {
+ clearState();
+ proxyConnector = Globals.getProxyConnector();
+ this.clientAddress = dest;
+ this.clientPort = port;
+ myLog.d("ProxyDataSocketFactory client port settings stored");
+ return true;
+ }
+
+ /**
+ * When the it's time for the SessionThread to actually begin PASV
+ * data transfer with the client, it will call this function to get
+ * a valid socket. The socket will have been created earlier with
+ * a call to onPasv(). The result of calling onTransfer() will be
+ * to cause the proxy to accept the incoming connection from the FTP
+ * client and start proxying back to us (the FTP server). The socket
+ * can then be handed back to the SessionThread which can use it as
+ * if it were directly connected to the client.
+ */
+ @Override
+ public Socket onTransfer() {
+ if(proxyConnector == null) {
+ myLog.w("Unexpected null proxyConnector in onTransfer");
+ return null;
+ }
+
+ if(socket == null) {
+ // We are in PORT mode (not PASV mode)
+ if(proxyConnector == null) {
+ myLog.l(Log.INFO, "Unexpected null proxyConnector in onTransfer");
+ return null;
+ }
+ // May return null, that's fine. ProxyConnector will log errors.
+ socket = proxyConnector.dataPortConnect(clientAddress, clientPort);
+ return socket;
+ } else {
+ // We are in PASV mode (not PORT mode)
+ if(proxyConnector.pasvAccept(socket)) {
+ return socket;
+ } else {
+ myLog.w("proxyConnector pasvAccept failed");
+ return null;
+ }
+ }
+ }
+
+ @Override
+ public void reportTraffic(long bytes) {
+ ProxyConnector pc = Globals.getProxyConnector();
+ if(pc == null) {
+ myLog.d("Can't report traffic, null ProxyConnector");
+ } else {
+ pc.incrementProxyUsage(bytes);
+ }
+ }
+}
diff --git a/src/main/java/org/swiftp/server/ProxyDataSocketInfo.java b/src/main/java/org/swiftp/server/ProxyDataSocketInfo.java
index e1ffe4feddfb608e91c17a4319fb40e06e1aa007..c1280c9078b95f22148112f277fe0b9ab8b8a23e 100644
--- a/src/main/java/org/swiftp/server/ProxyDataSocketInfo.java
+++ b/src/main/java/org/swiftp/server/ProxyDataSocketInfo.java
@@ -1,49 +1,49 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.net.Socket;
-
-public class ProxyDataSocketInfo extends Socket {
- private int remotePublicPort;
- private Socket socket;
-
- public Socket getSocket() {
- return socket;
- }
-
- public void setSocket(Socket socket) {
- this.socket = socket;
- }
-
- public ProxyDataSocketInfo(Socket socket, int remotePublicPort) {
- this.remotePublicPort = remotePublicPort;
- this.socket = socket;
- }
-
- public int getRemotePublicPort() {
- return remotePublicPort;
- }
-
- public void setRemotePublicPort(int remotePublicPort) {
- this.remotePublicPort = remotePublicPort;
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.net.Socket;
+
+public class ProxyDataSocketInfo extends Socket {
+ private int remotePublicPort;
+ private Socket socket;
+
+ public Socket getSocket() {
+ return socket;
+ }
+
+ public void setSocket(Socket socket) {
+ this.socket = socket;
+ }
+
+ public ProxyDataSocketInfo(Socket socket, int remotePublicPort) {
+ this.remotePublicPort = remotePublicPort;
+ this.socket = socket;
+ }
+
+ public int getRemotePublicPort() {
+ return remotePublicPort;
+ }
+
+ public void setRemotePublicPort(int remotePublicPort) {
+ this.remotePublicPort = remotePublicPort;
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/SessionThread.java b/src/main/java/org/swiftp/server/SessionThread.java
index 8343771d1df8f4b3e371449b6419a2c0f043362a..5fcb733680ef82d00720d900bcd68389aa8b9d13 100644
--- a/src/main/java/org/swiftp/server/SessionThread.java
+++ b/src/main/java/org/swiftp/server/SessionThread.java
@@ -1,445 +1,445 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
- */
-
-package org.swiftp.server;
-
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.InetAddress;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-
-import org.swiftp.Defaults;
-import org.swiftp.FTPServerService;
-import org.swiftp.Globals;
-import org.swiftp.MyLog;
-import org.swiftp.Util;
-
-import android.util.Log;
-
-public class SessionThread extends Thread {
- protected boolean shouldExit = false;
- protected Socket cmdSocket;
- protected MyLog myLog = new MyLog(getClass().getName());
- protected ByteBuffer buffer = ByteBuffer.allocate(Defaults
- .getInputBufferSize());
- protected boolean pasvMode = false;
- protected boolean binaryMode = false;
- protected Account account = new Account();
- protected boolean authenticated = false;
- protected File workingDir = Globals.getChrootDir();
- // protected ServerSocket dataServerSocket = null;
- protected Socket dataSocket = null;
- // protected FTPServerService service;
- protected File renameFrom = null;
- // protected InetAddress outDataDest = null;
- // protected int outDataPort = 20; // 20 is the default ftp-data port
- protected DataSocketFactory dataSocketFactory;
- OutputStream dataOutputStream = null;
- private boolean sendWelcomeBanner;
- protected String encoding = Defaults.SESSION_ENCODING;
- protected Source source;
- int authFails = 0;
-
- public enum Source {LOCAL, PROXY}; // where did this connection come from?
- public static int MAX_AUTH_FAILS = 3;
- /**
- * Used when we get a PORT command to open up an outgoing socket.
- *
- * @return
- */
- // public void setPortSocket(InetAddress dest, int port) {
- // myLog.l(Log.DEBUG, "Setting PORT dest to " +
- // dest.getHostAddress() + " port " + port);
- // outDataDest = dest;
- // outDataPort = port;
- // }
- /**
- * Sends a string over the already-established data socket
- *
- * @param string
- * @return Whether the send completed successfully
- */
- public boolean sendViaDataSocket(String string) {
- try {
- byte[] bytes = string.getBytes(encoding);
- myLog.d("Using data connection encoding: " + encoding);
- return sendViaDataSocket(bytes, bytes.length);
- } catch (UnsupportedEncodingException e) {
- myLog.l(Log.ERROR, "Unsupported encoding for data socket send");
- return false;
- }
- }
-
- public boolean sendViaDataSocket(byte[] bytes, int len) {
- return sendViaDataSocket(bytes, 0, len);
- }
-
- /**
- * Sends a byte array over the already-established data socket
- *
- * @param bytes
- * @param len
- * @return
- */
- public boolean sendViaDataSocket(byte[] bytes, int start, int len) {
-
- if (dataOutputStream == null) {
- myLog.l(Log.INFO, "Can't send via null dataOutputStream");
- return false;
- }
- if (len == 0) {
- return true; // this isn't an "error"
- }
- try {
- dataOutputStream.write(bytes, start, len);
- } catch (IOException e) {
- myLog.l(Log.INFO, "Couldn't write output stream for data socket");
- myLog.l(Log.INFO, e.toString());
- return false;
- }
- dataSocketFactory.reportTraffic(len);
- return true;
- }
-
- /**
- * Received some bytes from the data socket, which is assumed to already be
- * connected. The bytes are placed in the given array, and the number of
- * bytes successfully read is returned.
- *
- * @param bytes
- * Where to place the input bytes
- * @return>0 if successful which is the number of bytes read, -1 if no
- * bytes remain to be read, -2 if the data socket was not connected,
- * 0 if there was a read error
- */
- public int receiveFromDataSocket(byte[] buf) {
- int bytesRead;
-
- if (dataSocket == null) {
- myLog.l(Log.INFO, "Can't receive from null dataSocket");
- return -2;
- }
- if (!dataSocket.isConnected()) {
- myLog.l(Log.INFO, "Can't receive from unconnected socket");
- return -2;
- }
- InputStream in;
- try {
- in = dataSocket.getInputStream();
- // If the read returns 0 bytes, the stream is not yet
- // closed, but we just want to read again.
- while ((bytesRead = in.read(buf, 0, buf.length)) == 0) {
- }
- if (bytesRead == -1) {
- // If InputStream.read returns -1, there are no bytes
- // remaining, so we return 0.
- return -1;
- }
- } catch (IOException e) {
- myLog.l(Log.INFO, "Error reading data socket");
- return 0;
- }
- dataSocketFactory.reportTraffic(bytesRead);
- return bytesRead;
- }
-
- /**
- * Called when we receive a PASV command.
- *
- * @return Whether the necessary initialization was successful.
- */
- public int onPasv() {
- return dataSocketFactory.onPasv();
- }
-
- /**
- * Called when we receive a PORT command.
- *
- * @return Whether the necessary initialization was successful.
- */
- public boolean onPort(InetAddress dest, int port) {
- return dataSocketFactory.onPort(dest, port);
- }
-
- public InetAddress getDataSocketPasvIp() {
- // When the client sends PASV, our reply will contain the address and port
- // of the data connection that the client should connect to. For this purpose
- // we always use the same IP address that the command socket is using.
- return cmdSocket.getLocalAddress();
-
- // The old code, not totally correct.
- // return dataSocketFactory.getPasvIp();
- }
-
- // public int getDataSocketPort() {
- // return dataSocketFactory.getPortNumber();
- // }
-
- /**
- * Will be called by (e.g.) CmdSTOR, CmdRETR, CmdLIST, etc. when they are
- * about to start actually doing IO over the data socket.
- *
- * @return
- */
- public boolean startUsingDataSocket() {
- try {
- dataSocket = dataSocketFactory.onTransfer();
- if (dataSocket == null) {
- myLog.l(Log.INFO,
- "dataSocketFactory.onTransfer() returned null");
- return false;
- }
- dataOutputStream = dataSocket.getOutputStream();
- return true;
- } catch (IOException e) {
- myLog.l(Log.INFO,
- "IOException getting OutputStream for data socket");
- dataSocket = null;
- return false;
- }
- }
-
- public void quit() {
- myLog.d("SessionThread told to quit");
- closeSocket();
- }
-
- public void closeDataSocket() {
- myLog.l(Log.DEBUG, "Closing data socket");
- if (dataOutputStream != null) {
- try {
- dataOutputStream.close();
- } catch (IOException e) {
- }
- dataOutputStream = null;
- }
- if (dataSocket != null) {
- try {
- dataSocket.close();
- } catch (IOException e) {
- }
- }
- dataSocket = null;
- }
-
- protected InetAddress getLocalAddress() {
- return cmdSocket.getLocalAddress();
- }
-
- static int numNulls = 0;
- @Override
- public void run() {
- myLog.l(Log.INFO, "SessionThread started");
-
- if(sendWelcomeBanner) {
- writeString("220 SwiFTP " + Util.getVersion() + " ready\r\n");
- }
- // Main loop: read an incoming line and process it
- try {
- BufferedReader in = new BufferedReader(new InputStreamReader(cmdSocket
- .getInputStream()), 8192); // use 8k buffer
- while (true) {
- String line;
- line = in.readLine(); // will accept \r\n or \n for terminator
- if (line != null) {
- FTPServerService.writeMonitor(true, line);
- myLog.l(Log.DEBUG, "Received line from client: " + line);
- FtpCmd.dispatchCommand(this, line);
- } else {
- myLog.i("readLine gave null, quitting");
- break;
- }
- }
- } catch (IOException e) {
- myLog.l(Log.INFO, "Connection was dropped");
- }
- closeSocket();
- }
-
- /**
- * A static method to check the equality of two byte arrays, but only up to
- * a given length.
- */
- public static boolean compareLen(byte[] array1, byte[] array2, int len) {
- for (int i = 0; i < len; i++) { - if (array1[i] != array2[i]) { - return false; - } - } - return true; - } - - public void closeSocket() { - if (cmdSocket == null) { - return; - } - try { - cmdSocket.close(); - } catch (IOException e) {} - } - - public void writeBytes(byte[] bytes) { - try { - // TODO: do we really want to do all of this on each write? Why? - BufferedOutputStream out = new BufferedOutputStream(cmdSocket - .getOutputStream(), Defaults.dataChunkSize); - out.write(bytes); - out.flush(); - dataSocketFactory.reportTraffic(bytes.length); - } catch (IOException e) { - myLog.l(Log.INFO, "Exception writing socket"); - closeSocket(); - return; - } - } - - public void writeString(String str) { - FTPServerService.writeMonitor(false, str); - byte[] strBytes; - try { - strBytes = str.getBytes(encoding); - } catch (UnsupportedEncodingException e) { - myLog.e("Unsupported encoding: " + encoding); - strBytes = str.getBytes(); - } - writeBytes(strBytes); - } - - protected Socket getSocket() { - return cmdSocket; - } - - public Account getAccount() { - return account; - } - - public void setAccount(Account account) { - this.account = account; - } - - public boolean isPasvMode() { - return pasvMode; - } - - public SessionThread(Socket socket, DataSocketFactory dataSocketFactory, - Source source) { - this.cmdSocket = socket; - this.source = source; - this.dataSocketFactory = dataSocketFactory; - if(source == Source.LOCAL) { - this.sendWelcomeBanner = true; - } else { - this.sendWelcomeBanner = false; - } - } - - static public ByteBuffer stringToBB(String s) { - return ByteBuffer.wrap(s.getBytes()); - } - - public boolean isBinaryMode() { - return binaryMode; - } - - public void setBinaryMode(boolean binaryMode) { - this.binaryMode = binaryMode; - } - - public boolean isAuthenticated() { - return authenticated; - } - - public void authAttempt(boolean authenticated) { - if (authenticated) { - myLog.l(Log.INFO, "Authentication complete"); - this.authenticated = true; - } else { - // There was a failed auth attempt. If the connection came - // via the proxy, then drop it now. The client can't try again - // successfully because it doesn't know its real username. What - // it knows is prefix_username. - if(source == Source.PROXY) { - quit(); - } else { - authFails++; - myLog.i("Auth failed: " + authFails + "/" + MAX_AUTH_FAILS); - } - if(authFails> MAX_AUTH_FAILS) {
- myLog.i("Too many auth fails, quitting session");
- quit();
- }
- }
-
- }
-
- public File getWorkingDir() {
- return workingDir;
- }
-
- public void setWorkingDir(File workingDir) {
- try {
- this.workingDir = workingDir.getCanonicalFile().getAbsoluteFile();
- } catch (IOException e) {
- myLog.l(Log.INFO, "SessionThread canonical error");
- }
- }
-
- /*
- * public FTPServerService getService() { return service; }
- *
- * public void setService(FTPServerService service) { this.service =
- * service; }
- */
-
- public Socket getDataSocket() {
- return dataSocket;
- }
-
- public void setDataSocket(Socket dataSocket) {
- this.dataSocket = dataSocket;
- }
-
- // public ServerSocket getServerSocket() {
- // return dataServerSocket;
- // }
-
- public File getRenameFrom() {
- return renameFrom;
- }
-
- public void setRenameFrom(File renameFrom) {
- this.renameFrom = renameFrom;
- }
-
- public String getEncoding() {
- return encoding;
- }
-
- public void setEncoding(String encoding) {
- this.encoding = encoding;
- }
-
-}
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+ */
+
+package org.swiftp.server;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+import org.swiftp.Defaults;
+import org.swiftp.FTPServerService;
+import org.swiftp.Globals;
+import org.swiftp.MyLog;
+import org.swiftp.Util;
+
+import android.util.Log;
+
+public class SessionThread extends Thread {
+ protected boolean shouldExit = false;
+ protected Socket cmdSocket;
+ protected MyLog myLog = new MyLog(getClass().getName());
+ protected ByteBuffer buffer = ByteBuffer.allocate(Defaults
+ .getInputBufferSize());
+ protected boolean pasvMode = false;
+ protected boolean binaryMode = false;
+ protected Account account = new Account();
+ protected boolean authenticated = false;
+ protected File workingDir = Globals.getChrootDir();
+ // protected ServerSocket dataServerSocket = null;
+ protected Socket dataSocket = null;
+ // protected FTPServerService service;
+ protected File renameFrom = null;
+ // protected InetAddress outDataDest = null;
+ // protected int outDataPort = 20; // 20 is the default ftp-data port
+ protected DataSocketFactory dataSocketFactory;
+ OutputStream dataOutputStream = null;
+ private boolean sendWelcomeBanner;
+ protected String encoding = Defaults.SESSION_ENCODING;
+ protected Source source;
+ int authFails = 0;
+
+ public enum Source {LOCAL, PROXY}; // where did this connection come from?
+ public static int MAX_AUTH_FAILS = 3;
+ /**
+ * Used when we get a PORT command to open up an outgoing socket.
+ *
+ * @return
+ */
+ // public void setPortSocket(InetAddress dest, int port) {
+ // myLog.l(Log.DEBUG, "Setting PORT dest to " +
+ // dest.getHostAddress() + " port " + port);
+ // outDataDest = dest;
+ // outDataPort = port;
+ // }
+ /**
+ * Sends a string over the already-established data socket
+ *
+ * @param string
+ * @return Whether the send completed successfully
+ */
+ public boolean sendViaDataSocket(String string) {
+ try {
+ byte[] bytes = string.getBytes(encoding);
+ myLog.d("Using data connection encoding: " + encoding);
+ return sendViaDataSocket(bytes, bytes.length);
+ } catch (UnsupportedEncodingException e) {
+ myLog.l(Log.ERROR, "Unsupported encoding for data socket send");
+ return false;
+ }
+ }
+
+ public boolean sendViaDataSocket(byte[] bytes, int len) {
+ return sendViaDataSocket(bytes, 0, len);
+ }
+
+ /**
+ * Sends a byte array over the already-established data socket
+ *
+ * @param bytes
+ * @param len
+ * @return
+ */
+ public boolean sendViaDataSocket(byte[] bytes, int start, int len) {
+
+ if (dataOutputStream == null) {
+ myLog.l(Log.INFO, "Can't send via null dataOutputStream");
+ return false;
+ }
+ if (len == 0) {
+ return true; // this isn't an "error"
+ }
+ try {
+ dataOutputStream.write(bytes, start, len);
+ } catch (IOException e) {
+ myLog.l(Log.INFO, "Couldn't write output stream for data socket");
+ myLog.l(Log.INFO, e.toString());
+ return false;
+ }
+ dataSocketFactory.reportTraffic(len);
+ return true;
+ }
+
+ /**
+ * Received some bytes from the data socket, which is assumed to already be
+ * connected. The bytes are placed in the given array, and the number of
+ * bytes successfully read is returned.
+ *
+ * @param bytes
+ * Where to place the input bytes
+ * @return>0 if successful which is the number of bytes read, -1 if no
+ * bytes remain to be read, -2 if the data socket was not connected,
+ * 0 if there was a read error
+ */
+ public int receiveFromDataSocket(byte[] buf) {
+ int bytesRead;
+
+ if (dataSocket == null) {
+ myLog.l(Log.INFO, "Can't receive from null dataSocket");
+ return -2;
+ }
+ if (!dataSocket.isConnected()) {
+ myLog.l(Log.INFO, "Can't receive from unconnected socket");
+ return -2;
+ }
+ InputStream in;
+ try {
+ in = dataSocket.getInputStream();
+ // If the read returns 0 bytes, the stream is not yet
+ // closed, but we just want to read again.
+ while ((bytesRead = in.read(buf, 0, buf.length)) == 0) {
+ }
+ if (bytesRead == -1) {
+ // If InputStream.read returns -1, there are no bytes
+ // remaining, so we return 0.
+ return -1;
+ }
+ } catch (IOException e) {
+ myLog.l(Log.INFO, "Error reading data socket");
+ return 0;
+ }
+ dataSocketFactory.reportTraffic(bytesRead);
+ return bytesRead;
+ }
+
+ /**
+ * Called when we receive a PASV command.
+ *
+ * @return Whether the necessary initialization was successful.
+ */
+ public int onPasv() {
+ return dataSocketFactory.onPasv();
+ }
+
+ /**
+ * Called when we receive a PORT command.
+ *
+ * @return Whether the necessary initialization was successful.
+ */
+ public boolean onPort(InetAddress dest, int port) {
+ return dataSocketFactory.onPort(dest, port);
+ }
+
+ public InetAddress getDataSocketPasvIp() {
+ // When the client sends PASV, our reply will contain the address and port
+ // of the data connection that the client should connect to. For this purpose
+ // we always use the same IP address that the command socket is using.
+ return cmdSocket.getLocalAddress();
+
+ // The old code, not totally correct.
+ // return dataSocketFactory.getPasvIp();
+ }
+
+ // public int getDataSocketPort() {
+ // return dataSocketFactory.getPortNumber();
+ // }
+
+ /**
+ * Will be called by (e.g.) CmdSTOR, CmdRETR, CmdLIST, etc. when they are
+ * about to start actually doing IO over the data socket.
+ *
+ * @return
+ */
+ public boolean startUsingDataSocket() {
+ try {
+ dataSocket = dataSocketFactory.onTransfer();
+ if (dataSocket == null) {
+ myLog.l(Log.INFO,
+ "dataSocketFactory.onTransfer() returned null");
+ return false;
+ }
+ dataOutputStream = dataSocket.getOutputStream();
+ return true;
+ } catch (IOException e) {
+ myLog.l(Log.INFO,
+ "IOException getting OutputStream for data socket");
+ dataSocket = null;
+ return false;
+ }
+ }
+
+ public void quit() {
+ myLog.d("SessionThread told to quit");
+ closeSocket();
+ }
+
+ public void closeDataSocket() {
+ myLog.l(Log.DEBUG, "Closing data socket");
+ if (dataOutputStream != null) {
+ try {
+ dataOutputStream.close();
+ } catch (IOException e) {
+ }
+ dataOutputStream = null;
+ }
+ if (dataSocket != null) {
+ try {
+ dataSocket.close();
+ } catch (IOException e) {
+ }
+ }
+ dataSocket = null;
+ }
+
+ protected InetAddress getLocalAddress() {
+ return cmdSocket.getLocalAddress();
+ }
+
+ static int numNulls = 0;
+ @Override
+ public void run() {
+ myLog.l(Log.INFO, "SessionThread started");
+
+ if(sendWelcomeBanner) {
+ writeString("220 SwiFTP " + Util.getVersion() + " ready\r\n");
+ }
+ // Main loop: read an incoming line and process it
+ try {
+ BufferedReader in = new BufferedReader(new InputStreamReader(cmdSocket
+ .getInputStream()), 8192); // use 8k buffer
+ while (true) {
+ String line;
+ line = in.readLine(); // will accept \r\n or \n for terminator
+ if (line != null) {
+ FTPServerService.writeMonitor(true, line);
+ myLog.l(Log.DEBUG, "Received line from client: " + line);
+ FtpCmd.dispatchCommand(this, line);
+ } else {
+ myLog.i("readLine gave null, quitting");
+ break;
+ }
+ }
+ } catch (IOException e) {
+ myLog.l(Log.INFO, "Connection was dropped");
+ }
+ closeSocket();
+ }
+
+ /**
+ * A static method to check the equality of two byte arrays, but only up to
+ * a given length.
+ */
+ public static boolean compareLen(byte[] array1, byte[] array2, int len) {
+ for (int i = 0; i < len; i++) { + if (array1[i] != array2[i]) { + return false; + } + } + return true; + } + + public void closeSocket() { + if (cmdSocket == null) { + return; + } + try { + cmdSocket.close(); + } catch (IOException e) {} + } + + public void writeBytes(byte[] bytes) { + try { + // TODO: do we really want to do all of this on each write? Why? + BufferedOutputStream out = new BufferedOutputStream(cmdSocket + .getOutputStream(), Defaults.dataChunkSize); + out.write(bytes); + out.flush(); + dataSocketFactory.reportTraffic(bytes.length); + } catch (IOException e) { + myLog.l(Log.INFO, "Exception writing socket"); + closeSocket(); + return; + } + } + + public void writeString(String str) { + FTPServerService.writeMonitor(false, str); + byte[] strBytes; + try { + strBytes = str.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + myLog.e("Unsupported encoding: " + encoding); + strBytes = str.getBytes(); + } + writeBytes(strBytes); + } + + protected Socket getSocket() { + return cmdSocket; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public boolean isPasvMode() { + return pasvMode; + } + + public SessionThread(Socket socket, DataSocketFactory dataSocketFactory, + Source source) { + this.cmdSocket = socket; + this.source = source; + this.dataSocketFactory = dataSocketFactory; + if(source == Source.LOCAL) { + this.sendWelcomeBanner = true; + } else { + this.sendWelcomeBanner = false; + } + } + + static public ByteBuffer stringToBB(String s) { + return ByteBuffer.wrap(s.getBytes()); + } + + public boolean isBinaryMode() { + return binaryMode; + } + + public void setBinaryMode(boolean binaryMode) { + this.binaryMode = binaryMode; + } + + public boolean isAuthenticated() { + return authenticated; + } + + public void authAttempt(boolean authenticated) { + if (authenticated) { + myLog.l(Log.INFO, "Authentication complete"); + this.authenticated = true; + } else { + // There was a failed auth attempt. If the connection came + // via the proxy, then drop it now. The client can't try again + // successfully because it doesn't know its real username. What + // it knows is prefix_username. + if(source == Source.PROXY) { + quit(); + } else { + authFails++; + myLog.i("Auth failed: " + authFails + "/" + MAX_AUTH_FAILS); + } + if(authFails> MAX_AUTH_FAILS) {
+ myLog.i("Too many auth fails, quitting session");
+ quit();
+ }
+ }
+
+ }
+
+ public File getWorkingDir() {
+ return workingDir;
+ }
+
+ public void setWorkingDir(File workingDir) {
+ try {
+ this.workingDir = workingDir.getCanonicalFile().getAbsoluteFile();
+ } catch (IOException e) {
+ myLog.l(Log.INFO, "SessionThread canonical error");
+ }
+ }
+
+ /*
+ * public FTPServerService getService() { return service; }
+ *
+ * public void setService(FTPServerService service) { this.service =
+ * service; }
+ */
+
+ public Socket getDataSocket() {
+ return dataSocket;
+ }
+
+ public void setDataSocket(Socket dataSocket) {
+ this.dataSocket = dataSocket;
+ }
+
+ // public ServerSocket getServerSocket() {
+ // return dataServerSocket;
+ // }
+
+ public File getRenameFrom() {
+ return renameFrom;
+ }
+
+ public void setRenameFrom(File renameFrom) {
+ this.renameFrom = renameFrom;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String encoding) {
+ this.encoding = encoding;
+ }
+
+}
diff --git a/src/main/java/org/swiftp/server/TcpListener.java b/src/main/java/org/swiftp/server/TcpListener.java
index 87680c592d6a58635662d9e9d7993e4b20a0cd76..7f199104ff9081719d0617c62ade4471bdb5b421 100644
--- a/src/main/java/org/swiftp/server/TcpListener.java
+++ b/src/main/java/org/swiftp/server/TcpListener.java
@@ -1,67 +1,67 @@
-/*
-Copyright 2009 David Revell
-
-This file is part of SwiFTP.
-
-SwiFTP is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-SwiFTP is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with SwiFTP. If not, see .
-*/
-
-package org.swiftp.server;
-
-import java.net.ServerSocket;
-import java.net.Socket;
-
-import org.swiftp.FTPServerService;
-import org.swiftp.MyLog;
-
-import android.util.Log;
-
-public class TcpListener extends Thread {
- ServerSocket listenSocket;
- FTPServerService ftpServerService;
- MyLog myLog = new MyLog(getClass().getName());
-
- public TcpListener(ServerSocket listenSocket, FTPServerService ftpServerService) {
- this.listenSocket = listenSocket;
- this.ftpServerService = ftpServerService;
- }
-
- public void quit() {
- try {
- listenSocket.close(); // if the TcpListener thread is blocked on accept,
- // closing the socket will raise an exception
- } catch (Exception e) {
- myLog.l(Log.DEBUG, "Exception closing TcpListener listenSocket");
- }
- }
-
- @Override
- public void run() {
- try {
- while(true) {
-
- Socket clientSocket = listenSocket.accept();
- myLog.l(Log.INFO, "New connection, spawned thread");
- SessionThread newSession = new SessionThread(clientSocket,
- new NormalDataSocketFactory(),
- SessionThread.Source.LOCAL);
- newSession.start();
- ftpServerService.registerSessionThread(newSession);
- }
- } catch (Exception e) {
- myLog.l(Log.DEBUG, "Exception in TcpListener");
- }
- }
-}
-
+/*
+Copyright 2009 David Revell
+
+This file is part of SwiFTP.
+
+SwiFTP is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+SwiFTP is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with SwiFTP. If not, see .
+*/
+
+package org.swiftp.server;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.swiftp.FTPServerService;
+import org.swiftp.MyLog;
+
+import android.util.Log;
+
+public class TcpListener extends Thread {
+ ServerSocket listenSocket;
+ FTPServerService ftpServerService;
+ MyLog myLog = new MyLog(getClass().getName());
+
+ public TcpListener(ServerSocket listenSocket, FTPServerService ftpServerService) {
+ this.listenSocket = listenSocket;
+ this.ftpServerService = ftpServerService;
+ }
+
+ public void quit() {
+ try {
+ listenSocket.close(); // if the TcpListener thread is blocked on accept,
+ // closing the socket will raise an exception
+ } catch (Exception e) {
+ myLog.l(Log.DEBUG, "Exception closing TcpListener listenSocket");
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ while(true) {
+
+ Socket clientSocket = listenSocket.accept();
+ myLog.l(Log.INFO, "New connection, spawned thread");
+ SessionThread newSession = new SessionThread(clientSocket,
+ new NormalDataSocketFactory(),
+ SessionThread.Source.LOCAL);
+ newSession.start();
+ ftpServerService.registerSessionThread(newSession);
+ }
+ } catch (Exception e) {
+ myLog.l(Log.DEBUG, "Exception in TcpListener");
+ }
+ }
+}
+
diff --git a/src/main/java/util/DocumentUtil.java b/src/main/java/util/DocumentUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..abfd1f54233728bfe11fb2e849abc4849e942a1d
--- /dev/null
+++ b/src/main/java/util/DocumentUtil.java
@@ -0,0 +1,520 @@
+package util;
+//by 乘着船 at 2021-2023
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.preference.PreferenceManager;
+import android.support.v4.provider.DocumentFile;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class DocumentUtil {
+
+ private static final String TAG = DocumentUtil.class.getSimpleName();
+
+ public static final int OPEN_DOCUMENT_TREE_CODE = 8000;
+ public static final int MAX_BUFFER_SIZE = 5242880;//max buffer size 5MB
+
+ public static final String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
+ public static final String SDCARD_PATH = SDCARD + "/";
+ public static final String CONTENT_PRF = "content://";
+ public static final String[] SDCARD_CONTENT = new String[]{
+ CONTENT_PRF + "com.android.externalstorage.documents/tree/primary%3A", "/document/primary%3A"};
+ public static final String ANDROID_PATH = SDCARD_PATH + "Android/";
+ public static final String[] ANDROID_CONTENT = new String[]{
+ SDCARD_CONTENT[0] + "Android%2F", SDCARD_CONTENT[1] + "Android%2F"};
+
+ public static final int ANDROID_SAVE_INTENT =
+ Intent.FLAG_GRANT_READ_URI_PERMISSION |
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION ;
+ public static final int ANDROID_OPEN_INTENT =
+ Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
+ Intent.FLAG_GRANT_PREFIX_URI_PERMISSION |
+ ANDROID_SAVE_INTENT ;
+
+ public static List sExtSdCardPaths = new ArrayList();
+
+ private static String requestRootPath = null;
+
+ private DocumentUtil() {
+
+ }
+
+ public static void cleanCache() {
+ sExtSdCardPaths.clear();
+ }
+
+ /**
+ * Get a list of external SD card paths. (Kitkat or higher.)
+ *
+ * @return A list of external SD card paths.
+ */
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private static String[] getExtSdCardPaths(Context context) {
+ if (sExtSdCardPaths.size()> 0) {
+ return sExtSdCardPaths.toArray(new String[0]);
+ }
+ for (File file : context.getExternalFilesDirs("external")) {
+ if (file != null && !file.equals(context.getExternalFilesDir("external"))) {
+ int index = file.getAbsolutePath().lastIndexOf("/Android/data");
+ if (index < 0) { + Log.d(TAG, "Unexpected external file dir: " + file.getAbsolutePath()); + } else { + String path = file.getAbsolutePath().substring(0, index); + try { + path = new File(path).getCanonicalPath(); + } catch (IOException e) { + // Keep non-canonical path. + } + sExtSdCardPaths.add(path); + } + } + } + if (sExtSdCardPaths.isEmpty()) sExtSdCardPaths.add(SDCARD); + return sExtSdCardPaths.toArray(new String[0]); + } + + /** + * Determine the main folder of the external SD card containing the given file. + * + * @param file the file. + * @return The main folder of the external SD card containing this file, if the file is on an SD + * card. Otherwise, + * null is returned. + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + private static String getExtSdCardFolder(final File file, Context context) { + String[] extSdPaths = getExtSdCardPaths(context); + try { + for (String extSdPath : extSdPaths) { + if (file.getCanonicalPath().startsWith(extSdPath)) { + return extSdPath; + } + } + } catch (IOException e) { + return null; + } + return null; + } + + /** + * Determine if a file is on external sd card. (Kitkat or higher.) + * + * @param file The file. + * @return true if on external sd card. + */ + @TargetApi(Build.VERSION_CODES.KITKAT) + public static boolean isOnExtSdCard(final File file, Context c) { + return getExtSdCardFolder(file, c) != null; + } + + /** + * Get a DocumentFile corresponding to the given file (for writing on ExtSdCard on Android 5). + * If the file is not + * existing, it is created. + * + * @param file The file. + * @param isDirectory true/false/null + * true/false - flag indicating if the file should be a directory , + * if file not exist, it will be create . + * null - do not know the file is a directory , + * if file not exist, it will not be create . + * @return The DocumentFile + */ + public static DocumentFile getDocumentFile( + final File file, final Boolean isDirectory, Context context) { + + String baseFolder = getExtSdCardFolder(file, context); + boolean originalDirectory = false; + if (baseFolder == null) { + return null; + } + + String relativePath = null; + try { + String fullPath = file.getCanonicalPath(); + if (!baseFolder.equals(fullPath)) { + relativePath = fullPath.substring(baseFolder.length() + 1); + } else { + originalDirectory = true; + } + } catch (IOException e) { + return null; + } catch (Exception f) { + originalDirectory = true; + //continue + } + String as = PreferenceManager.getDefaultSharedPreferences(context).getString(baseFolder, + null); + + Uri treeUri = null; + if (as != null) treeUri = Uri.parse(as); + if (treeUri == null) { + return null; + } + + // start with root of SD card and then parse through document tree. + DocumentFile document = DocumentFile.fromTreeUri(context, treeUri); + if (originalDirectory) return document; + String[] parts = relativePath.split("/"); + for (int i = 0; i < parts.length; i++) { + DocumentFile nextDocument = document.findFile(parts[i]); + + if (nextDocument == null) { + if(isDirectory == null) return null; + if ((i < parts.length - 1) || isDirectory) { + nextDocument = document.createDirectory(parts[i]); + } else { + nextDocument = document.createFile("image", parts[i]); + } + } + document = nextDocument; + } + + return document; + } + + public static boolean mkdirs(Context context, File dir) { + boolean res = dir.mkdirs(); + if (!res) { + if (isOnExtSdCard(dir, context)) { + DocumentFile documentFile = getDocumentFile(dir, true, context); + res = documentFile != null && documentFile.canWrite(); + } + } + return res; + } + + private static boolean FileDelete(File file) { + if (file.isFile()) return file.delete(); + File[] subFiles = file.listFiles(); + if (subFiles==null || subFiles.length==0) + return file.delete(); + boolean ret = true; + for(File subFile:subFiles){ + if (subFile.isDirectory()) ret = FileDelete(subFile) && ret; + else ret = subFile.delete() && ret; + } + return ret && file.delete(); + } + + public static boolean delete(Context context, File file) { + boolean ret = FileDelete(file); + if (ret) return ret; + //if (isOnExtSdCard(file, context)) { + DocumentFile f = getDocumentFile(file, false, context); + if (f != null) { + ret = f.delete(); + } + //} + return ret; + } + + public static boolean canWrite(File file) { + boolean res = file.exists() && file.canWrite(); + + if (!res && !file.exists()) { + try { + if (!file.isDirectory()) { + res = file.createNewFile() && file.delete(); + } else { + res = file.mkdirs() && file.delete(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return res; + } + + /*public static boolean canWrite(Context context, File file) { + boolean res = canWrite(file); + + if (!res && isOnExtSdCard(file, context)) { + DocumentFile documentFile = getDocumentFile(file, true, context); + res = documentFile != null && documentFile.canWrite(); + } + return res; + }*/ + + private static boolean renameToCross(Context context,File src,File dest) throws Exception { + copy(context,src,dest); + boolean exist = dest.exists(); + if (!exist) { + DocumentFile Dest = getDocumentFile(dest,null,context); + if(Dest!=null && Dest.exists()) + exist = true; + } + delete(context,src); + return exist; + } + + public static boolean renameTo(Context context, File src, File dest) throws Exception { + boolean res = src.renameTo(dest); + if (res) return true; + + if (isOnExtSdCard(dest, context)) { + DocumentFile srcDoc; + if (isOnExtSdCard(src, context)) { + srcDoc = getDocumentFile(src, false, context); + } else { + srcDoc = DocumentFile.fromFile(src); + } + DocumentFile destDoc = getDocumentFile(dest.getParentFile(), true, context); + + if (srcDoc != null && destDoc != null) { + if(!srcDoc.exists()) + throw new FileNotFoundException(src.toString()); + if (Objects.equals(src.getParent(), dest.getParent())) { + DocumentFile DestDoc = getDocumentFile(dest, null, context); + if(DestDoc!=null && DestDoc.exists()){ + if(dest.isDirectory()) + return renameToCross(context,src,dest); + else if(!DestDoc.delete()) + return false; + } + res = srcDoc.renameTo(dest.getName()); + } else return renameToCross(context, src, dest); + } else { + if ((src.exists() || srcDoc!=null) && (dest.canWrite() || destDoc!=null)) + return renameToCross(context, src, dest); + else return false; + } + } else return renameToCross(context, src, dest); + + return res; + } + + public static InputStream getInputStream(Context context, File destFile) { + InputStream in = null; + try { + if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) { + DocumentFile file = getDocumentFile(destFile, false, context); + if (file != null && file.canWrite()) { + in = context.getContentResolver().openInputStream(file.getUri()); + } + } else { + in = new FileInputStream(destFile); + + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return in; + } + + public static OutputStream getOutputStream(Context context, File destFile, boolean append) { + OutputStream out = null; + try { + if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) { + DocumentFile file = getDocumentFile(destFile, false, context); + if (file != null && file.canWrite()) { + String mode; + if(append) + mode = "wa"; + else mode = "wt"; + out = context.getContentResolver().openOutputStream(file.getUri(), mode); + } + } else { + out = new FileOutputStream(destFile, append); + + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return out; + } + + public static OutputStream getOutputStream(Context context, File destFile) { + OutputStream out = null; + try { + if (!canWrite(destFile) && isOnExtSdCard(destFile, context)) { + DocumentFile file = getDocumentFile(destFile, false, context); + if (file != null && file.canWrite()) + out = context.getContentResolver().openOutputStream(file.getUri(),"wt"); + } else { + out = new FileOutputStream(destFile); + + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + return out; + } + + public static void saveTreeUri(Context context, String rootPath, Uri uri) { + DocumentFile file = DocumentFile.fromTreeUri(context, uri); + if (file != null && file.canWrite()) { + SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context); + perf.edit().putString(rootPath, uri.toString()).apply(); + } else { + Log.e(TAG, "no write permission: " + rootPath); + } + } + + public static boolean checkWritableRootPath(Context context, String rootPath) { + File root = new File(rootPath); + if (!root.canWrite()) { + + if (isOnExtSdCard(root, context)) { + DocumentFile documentFile = getDocumentFile(root, true, context); + return documentFile == null || !documentFile.canWrite(); + } else { + SharedPreferences perf = PreferenceManager.getDefaultSharedPreferences(context); + + String documentUri = perf.getString(rootPath, ""); + + if (documentUri == null || documentUri.isEmpty()) { + return true; + } else { + DocumentFile file = DocumentFile.fromTreeUri(context, Uri.parse(documentUri)); + return !(file != null && file.canWrite()); + } + } + } + return false; + } + + private static void copyFile ( + Context context, File srcFile, File destFile) throws Exception { + InputStream fis=getInputStream(context,srcFile); + if (fis==null) fis=new FileInputStream(srcFile); + OutputStream fos=getOutputStream(context,destFile); + if (fos==null) fos=new FileOutputStream(destFile); + int len = fis.available(); + if (len>MAX_BUFFER_SIZE) len=MAX_BUFFER_SIZE;
+ byte[] flush =new byte [len];
+ while((len=fis.read(flush))>0) {
+ fos.write(flush,0,len);
+ }
+ fos.flush();
+ fis.close();
+ fos.close();
+ }
+
+ private static void copyTree (
+ Context context,File srcFolder,File destFolder)
+ throws Exception{
+ DocumentFile DestFolder=getDocumentFile(destFolder,true,context);
+ if (DestFolder==null) destFolder.mkdirs();
+ DocumentFile SrcFolder=getDocumentFile(srcFolder,true,context);
+ String name;
+ File srcSub,destSub;
+ if (SrcFolder != null) {
+ DocumentFile[] SrcSubs = SrcFolder.listFiles();
+ for(DocumentFile SrcSub:SrcSubs) {
+ name = SrcSub.getName();
+ srcSub=new File(srcFolder.getAbsolutePath(), name);
+ destSub=new File(destFolder.getAbsolutePath(), name);
+ if (SrcSub.isDirectory()){
+ copyTree(context,srcSub,destSub);
+ } else {
+ copyFile(context,srcSub,destSub);
+ }
+ }
+ } else {
+ File[] SrcSubs = srcFolder.listFiles();
+ for(File SrcSub:SrcSubs) {
+ name = SrcSub.getName();
+ srcSub=new File(srcFolder.getAbsolutePath(), name);
+ destSub=new File(destFolder.getAbsolutePath(), name);
+ if (SrcSub.isDirectory()){
+ copyTree(context,srcSub,destSub);
+ } else {
+ copyFile(context,srcSub,destSub);
+ }
+ }
+ }
+ }
+
+ public static void copy (
+ Context context, File src, File dest)
+ throws Exception{
+ if (isDirectory(context,src))
+ copyTree(context,src,dest);
+ else copyFile(context,src,dest);
+ }
+
+ public static String[] listFiles (
+ Context context,File folder)
+ throws Exception{
+ DocumentFile Folder=getDocumentFile(folder,true,context);
+ if (Folder == null) {
+ File[] Subs = folder.listFiles();
+ if (Subs==null) return null;
+ String[] subs=new String[Subs.length];
+ for (int i=0;i fileUtils = Class.forName("android.os.FileUtils");
+ Method setPermissions =
+ fileUtils.getMethod("setPermissions", String.class, int.class, int.class, int.class);
+ return (Integer) setPermissions.invoke(null, path.getAbsolutePath(), mode, -1, -1);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static void setPermission(File file) throws IOException {
+ Set perms = new HashSet();
+ perms.add(PosixFilePermission.OWNER_READ);
+ perms.add(PosixFilePermission.OWNER_WRITE);
+ perms.add(PosixFilePermission.OWNER_EXECUTE);
+
+ perms.add(PosixFilePermission.OTHERS_READ);
+ perms.add(PosixFilePermission.OTHERS_WRITE);
+ perms.add(PosixFilePermission.OWNER_EXECUTE);
+
+ perms.add(PosixFilePermission.GROUP_READ);
+ perms.add(PosixFilePermission.GROUP_WRITE);
+ perms.add(PosixFilePermission.GROUP_EXECUTE);
+
+ Files.setPosixFilePermissions(file.toPath(), perms);
+ }
+
+ public static boolean recursiveChmod(File root, int mode) throws Exception {
+ boolean success = chmod(root, mode) == 0;
+ for (File path : root.listFiles()) {
+ if (path.isDirectory()) {
+ success = recursiveChmod(path, mode);
+ }
+ success &= (chmod(path, mode) == 0);
+ }
+ return success;
+ }
+
+ public static boolean delete(String path) {
+ return delete(new File(path));
+ }
+
+ public static boolean delete(File path) {
+ boolean result = true;
+ if (path.canWrite()) {
+ if (path.isDirectory()) {
+ for (File child : path.listFiles()) {
+ result &= delete(child);
+ }
+ result &= path.delete(); // Delete empty directory.
+ } else if (path.isFile()) {
+ result = path.delete();
+ }
+ //if (!result) {
+ //Log.e(TAG, "Delete failed;");
+ //}
+ return result;
+ } else {
+ if (path.exists()) {
+ DocumentFile documentFile = DocumentUtil.getDocumentFile(path,null,activity);
+ if(documentFile!=null)
+ return documentFile.delete();
+ else return false;
+ } else {
+ //Log.e(TAG, "File does not exist.");
+ return false;
+ }
+ }
+ }
+
+ public static File copyFromStream(String name, InputStream input) {
+ if (name == null || name.length() == 0) {
+ //Log.e(TAG, "No script name specified.");
+ return null;
+ }
+ File file = new File(name);
+ if (!makeDirectories(file.getParentFile(), 0755)) {
+ return null;
+ }
+ try {
+ OutputStream output = new FileOutputStream(file);
+ IoUtil.copy(input, output);
+ } catch (Exception e) {
+ //Log.e(TAG, e);
+ return null;
+ }
+ return file;
+ }
+
+ public static boolean makeDirectories(File directory, int mode) {
+ File parent = directory;
+ while (parent.getParentFile() != null && !parent.exists()) {
+ parent = parent.getParentFile();
+ }
+ if (!directory.exists()) {
+ //Log.d(TAG, "Creating directory: " + directory.getName());
+ if (!directory.mkdirs()) {
+ //Log.e(TAG, "Failed to create directory.");
+ return false;
+ }
+ }
+ try {
+ recursiveChmod(parent, mode);
+ } catch (Exception e) {
+ //Log.e(TAG, e);
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean mkdir(File directory) {
+ boolean result;
+ if (directory.exists())
+ return false;
+ result = directory.mkdirs();
+ if(!result) {
+ DocumentFile documentFile = DocumentUtil.getDocumentFile(directory, true, activity);
+ result = documentFile != null && documentFile.exists();
+ }
+ return result;
+ }
+
+ public static File getExternalDownload() {
+ try {
+ Class> c = Class.forName("android.os.Environment");
+ Method m = c.getDeclaredMethod("getExternalStoragePublicDirectory", String.class);
+ String download = c.getDeclaredField("DIRECTORY_DOWNLOADS").get(null).toString();
+ return (File) m.invoke(null, download);
+ } catch (Exception e) {
+ return new File(Environment.getExternalStorageDirectory(), "Download");
+ }
+ }
+
+ public static boolean rename(String oldPath, String newPath) {
+ return rename(new File(oldPath),new File(newPath));
+ }
+
+ public static boolean rename(File file, String name) {
+ return rename(file,new File(file.getParent(), name));
+ }
+ public static boolean rename(File oldFile,File newFile) {
+ boolean result =oldFile.renameTo(newFile);
+ if(!result && oldFile.exists()) {
+ try {
+ DocumentFile documentFile = DocumentUtil.getDocumentFile(oldFile,null,activity);
+ if (documentFile != null)
+ result = documentFile.renameTo(newFile.getName());
+ } catch (Exception exception) {
+ exception.printStackTrace();
+ }
+ }
+ return result;
+ }
+
+ public static String readToString(File file) throws IOException {
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ FileReader reader = new FileReader(file);
+ StringBuilder out = new StringBuilder();
+ char[] buffer = new char[1024 * 4];
+ int numRead = 0;
+ while ((numRead = reader.read(buffer))> -1) {
+ out.append(String.valueOf(buffer, 0, numRead));
+ }
+ reader.close();
+ return out.toString();
+ }
+
+ public static String readFromAssetsFile(Context context, String name) throws IOException {
+ AssetManager am = context.getAssets();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(am.open(name)));
+ String line;
+ StringBuilder builder = new StringBuilder();
+ while ((line = reader.readLine()) != null) {
+ builder.append(line);
+ }
+ reader.close();
+ return builder.toString();
+ }
+
+ public static void lnOrcopy(File src, File dst, int sdk) throws IOException, ErrnoException {
+
+ if (sdk>=21) {
+ Os.symlink(src.getAbsolutePath(), dst.getAbsolutePath());
+ } else {
+ FileInputStream inStream = new FileInputStream(src);
+ FileOutputStream outStream = new FileOutputStream(dst);
+ FileChannel inChannel = inStream.getChannel();
+ FileChannel outChannel = outStream.getChannel();
+ inChannel.transferTo(0, inChannel.size(), outChannel);
+ inStream.close();
+ outStream.close();
+ }
+ }
+
+ public static String getFileContents(String filename) {
+
+ File scriptFile = new File( filename );
+ StringBuilder tContent = new StringBuilder();
+ if (scriptFile.exists()) {
+ BufferedReader in;
+ try {
+ in = new BufferedReader(new FileReader(scriptFile));
+ String line;
+
+ while ((line = in.readLine())!=null) {
+ tContent.append(line).append("\n");
+ }
+ in.close();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ return tContent.toString();
+ }
+
+ public static String getFileContents(String filename, int pos) {
+
+ File scriptFile = new File( filename );
+ StringBuilder tContent = new StringBuilder();
+ if (scriptFile.exists()) {
+ BufferedReader in;
+ try {
+ in = new BufferedReader(new FileReader(scriptFile));
+ String line;
+
+ while ((line = in.readLine())!=null) {
+ tContent.append(line).append("\n");
+ if (tContent.length()>=pos) {
+ in.close();
+ return tContent.toString();
+ }
+ }
+ in.close();
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ }
+ return tContent.toString();
+ }
+
+ public static boolean canWrite(File file){
+ boolean canWrite = file.canWrite();
+ if(!canWrite){
+ DocumentFile documentFile = DocumentUtil.getDocumentFile(file,false, activity);
+ if(documentFile!=null)
+ canWrite = documentFile.canWrite();
+ }
+ return canWrite;
+ }
+
+ public static FileOutputStream getFileOutputStream(File file,boolean append){
+ FileOutputStream fileOutputStream;
+ try {
+ fileOutputStream = new FileOutputStream(file, append);
+ } catch (IOException e){
+ fileOutputStream = (FileOutputStream) DocumentUtil.getOutputStream(activity,file,append);
+ }
+ return fileOutputStream;
+ }
+
+ public static FileOutputStream getFileOutputStream(File file){
+ FileOutputStream fileOutputStream;
+ try {
+ fileOutputStream = new FileOutputStream(file);
+ } catch (IOException e){
+ fileOutputStream = (FileOutputStream) DocumentUtil.getOutputStream(activity,file);
+ }
+ return fileOutputStream;
+ }
+
+ public static void writeToFile(String filePath, String text, boolean append) {
+ FileOutputStream fOut;
+ try{
+ try {
+ fOut = new FileOutputStream(filePath,append);
+ fOut.write(text.getBytes());
+ } catch (IOException e) {
+ DocumentFile file = DocumentUtil.getDocumentFile(new File(filePath), false, activity);
+ String mode;
+ if(append)
+ mode = "wa";
+ else mode = "wt";
+ fOut = (FileOutputStream) activity.getContentResolver().openOutputStream(file.getUri(),mode);
+ fOut.write(text.getBytes());
+ }
+ fOut.flush();
+ fOut.close();
+ } catch (IOException iox) {
+ iox.printStackTrace();
+ }
+ }
+
+ public static void writeToFile(String filePath, String text) {
+ FileOutputStream fOut;
+ try{
+ try {
+ fOut = new FileOutputStream(filePath);
+ fOut.write(text.getBytes());
+ } catch (IOException e) {
+ DocumentFile file = DocumentUtil.getDocumentFile(new File(filePath), false, activity);
+ fOut = (FileOutputStream) activity.getContentResolver().openOutputStream(file.getUri(),"wt");
+ fOut.write(text.getBytes());
+ }
+ fOut.flush();
+ fOut.close();
+ } catch (IOException iox) {
+ iox.printStackTrace();
+ }
+ }
+
+ public static String getAbsolutePath(Context context){
+// return context.getExternalFilesDir(null).getPath() + "/qpython";
+ //return Environment.getExternalStorageDirectory().getPath() + "/qpython";
+ return context.getExternalFilesDir("").getPath();
+ }
+
+ public static File getPath(Context context){
+ return context.getExternalFilesDir(null);
+ }
+
+ public static String getQyPath(){
+// return context.getExternalFilesDir(null).getAbsolutePath();
+ return Environment.getExternalStorageDirectory().getPath();
+ }
+
+ public static String getLibDownloadTempPath(Context context){
+ return getAbsolutePath(context) + "/cache";
+ }
+
+ public static String getAbsoluteLogPath(Context context){
+ return getAbsolutePath(context) + "/log/last.log";
+ }
+
+ public static String getPyCachePath(Context context){
+ return getAbsolutePath(context) + ".qpyc";
+ }
+}
diff --git a/src/main/java/util/IoUtil.java b/src/main/java/util/IoUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c33628a1bd3851eb4914d136f03e3843f81592f
--- /dev/null
+++ b/src/main/java/util/IoUtil.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package util;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class IoUtil {
+ private static final int BUFFER_SIZE = 1024 * 8;
+
+ private IoUtil() {
+ // Utility class.
+ }
+
+ public static int copy(InputStream input, OutputStream output) throws Exception, IOException {
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
+ BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE);
+ int count = 0, n = 0;
+ try {
+ while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
+ out.write(buffer, 0, n);
+ count += n;
+ }
+ out.flush();
+ } finally {
+ try {
+ out.close();
+ } catch (IOException e) {
+ //Log.e(e.getMessage(), e);
+ }
+ try {
+ in.close();
+ } catch (IOException e) {
+ //Log.e(e.getMessage(), e);
+ }
+ }
+ return count;
+ }
+
+}
diff --git a/src/main/res/layout/activity_preference.xml b/src/main/res/layout/activity_preference.xml
index 902b8ed1f8ab8a0e87b4be52b02f5cb4f1835f56..353016bea815c62631875f591b65c27b00ee2358 100644
--- a/src/main/res/layout/activity_preference.xml
+++ b/src/main/res/layout/activity_preference.xml
@@ -1,35 +1,35 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/layout/ftp_widget.xml b/src/main/res/layout/ftp_widget.xml
index 5b618383b9753296702503171551d62a2186283c..fa3c309e8443532acec9d3a6883a759f49e943e2 100644
--- a/src/main/res/layout/ftp_widget.xml
+++ b/src/main/res/layout/ftp_widget.xml
@@ -1,29 +1,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/values-v11/styles.xml b/src/main/res/values-v11/styles.xml
index 3c02242ad044be9b8c7c09e7c90c5d427763fe97..cab48cd670ce9b9e84847a3fa477b05245b09cd0 100644
--- a/src/main/res/values-v11/styles.xml
+++ b/src/main/res/values-v11/styles.xml
@@ -1,11 +1,11 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/main/res/values-v14/styles.xml b/src/main/res/values-v14/styles.xml
index a91fd0372b20f85e284fc2fa2ce949176dfdf6e5..b6dd86ae00edeb2e33b8a72b472043a8f36c0236 100644
--- a/src/main/res/values-v14/styles.xml
+++ b/src/main/res/values-v14/styles.xml
@@ -1,12 +1,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index 053a878ae3e1cf76de85bd94657333a1073873ea..7f9e5fb9b158be60eee2f985de793ab6361b7024 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -1,58 +1,57 @@
-
-QFtplib
-FTP 服务器
-运行
-启动 FTP 服务器
-FTP 服务运行在 %s
-无法启动 FTP 服务器
-设置
-用户名
-ftp
-密码
-ftp
-
- 显示密码
-
-
- 假
-
-高级设置...
-端口号
-2121
-停留在文件夹中
-/
-接受来自 WiFi 连接
-true
-接受来自网络代理连接
-false
-保持设备处于唤醒状态(全CPU速度)
-true
-额外
-帮助
-关于
-FTP 服务器帮助
-帮助信息
-FTP 服务器关于
-有关消息
-
-
-不能解析网址
-未知
-OK
-用户名必须由1个或多个字母,不允许使用其他字符。
-该密码必须由1个或多个字母数字字符,不允许使用其他字符。
-该端口号必须在范围为1~65535
-WifiStateReceiver
-FTP服务器启动
-FTP服务器正在运行
-服务器接受 FTP 连接
-连接
-已连接
-失败
-无法访问
-断开的
-警告:存储不可用,你可能想卸载它
-FTP服务器控件
-扩展功能
-
-
+
+QFtplib
+FTP 服务器
+运行
+启动 FTP 服务器
+FTP 服务运行在 %s
+无法启动 FTP 服务器
+设置
+用户名
+ftp
+密码
+ftp
+
+ 显示密码
+
+
+ 假
+
+高级设置...
+端口号
+停留在文件夹中
+/
+接受来自 WiFi 连接
+true
+接受来自网络代理连接
+false
+保持设备处于唤醒状态(全CPU速度)
+true
+额外
+帮助
+关于
+FTP 服务器帮助
+帮助信息
+FTP 服务器关于
+有关消息
+
+
+不能解析网址
+未知
+OK
+用户名必须由1个或多个字母,不允许使用其他字符。
+该密码必须由1个或多个字母数字字符,不允许使用其他字符。
+该端口号必须在范围为1~65535
+WifiStateReceiver
+FTP服务器启动
+FTP服务器正在运行
+服务器接受 FTP 连接
+连接
+已连接
+失败
+无法访问
+断开的
+警告:存储不可用,你可能想卸载它
+FTP服务器控件
+扩展功能
+
+
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index 32f90ce0571ac184dc14a7cd99cd7bc517118604..17016d2d28afdff698336a7c11eab593a3a4b292 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -1,7 +1,7 @@
-
-
- #FF4A4A4A
- #FF363636
- #FF4BAC07
- #FFFFFF
+
+
+ #FF4A4A4A
+ #FF363636
+ #FF4BAC07
+ #FFFFFF
\ No newline at end of file
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index fad07f2f80134163c682e4966dfb4e2383fbc8b7..6405565dc140535064cdbf48ce58b0153567eddb 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -1,72 +1,72 @@
-
- QFtplib
- FTP Server
- Running
- Start FTP server
- FTP server running at %s
- Failed to start the FTP server
- Settings
- Username
- ftp
- Password
- ftp
-
- Display password
-
-
- false
-
- Advanced Settings...
- Port Number
- 2121
- Stay in folder
- /
- Accept connection from wifi
- true
- Accept connection from net proxy
- false
- Keep device awake (full CPU speed)
- true
- Extra
- Help...
- About...
- FTP Server Help
- HELP Message
- FTP Server About
- ABOUT Message
-
-
- Can\'t retreive url
- Notice
-
- unknown
- OK
- The username must consist of 1 or more letters. No other characters are allowed.
- The password must consist of 1 or more alphanumeric characters. No other characters are allowed.
- The port number must be in the range 1 to 65535.
- WifiStateReceiver
- FTP Server started
- FTP Server is running
- Server is accepting FTP connections.
- connecting
- connected
- failed
- unreachable
- disconnected
- Warning: storage is not available. You may want to unmount it.
- FTP Server Widget
- FTP setting
- FTP Server Setting
- Extensions
-
-
- reset_storage
- running_state
- username
- password
- portNum
- show_password
- chrootDir
- stayAwake
- about
-
+
+ QFtplib
+ FTP Server
+ Running
+ Start FTP server
+ FTP server running at %s
+ Failed to start the FTP server
+ Settings
+ Username
+ ftp
+ Password
+ ftp
+
+ Display password
+
+
+ false
+
+ Advanced Settings...
+ Port Number
+ 2121
+ Stay in folder
+ /
+ Accept connection from wifi
+ true
+ Accept connection from net proxy
+ false
+ Keep device awake (full CPU speed)
+ true
+ Extra
+ Help...
+ About...
+ FTP Server Help
+ HELP Message
+ FTP Server About
+ ABOUT Message
+
+
+ Can\'t retreive url
+ Notice
+
+ unknown
+ OK
+ The username must consist of 1 or more letters. No other characters are allowed.
+ The password must consist of 1 or more alphanumeric characters. No other characters are allowed.
+ The port number must be in the range 1 to 65535.
+ WifiStateReceiver
+ FTP Server started
+ FTP Server is running
+ Server is accepting FTP connections.
+ connecting
+ connected
+ failed
+ unreachable
+ disconnected
+ Warning: storage is not available. You may want to unmount it.
+ FTP Server Widget
+ FTP setting
+ FTP Server Setting
+ Extensions
+
+
+ reset_storage
+ running_state
+ username
+ password
+ portNum
+ show_password
+ chrootDir
+ stayAwake
+ about
+
diff --git a/src/main/res/values/styles.xml b/src/main/res/values/styles.xml
index 59aca12c932f78b2095522e27bd9852fb6ef938b..c6d69f5039b5a3f71556eaf9c7b9c577641db018 100644
--- a/src/main/res/values/styles.xml
+++ b/src/main/res/values/styles.xml
@@ -1,26 +1,26 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/res/xml/ftp_preferences.xml b/src/main/res/xml/ftp_preferences.xml
index d85d42fd936851f9ae68e6c6378896b6b5c1657e..aeb50739145c92d10ec8f380fbcb0cd155a06fe5 100644
--- a/src/main/res/xml/ftp_preferences.xml
+++ b/src/main/res/xml/ftp_preferences.xml
@@ -1,18 +1,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/res/xml/header_preferences.xml b/src/main/res/xml/header_preferences.xml
index a856f7b83a3fcd97d1c15b92b32fe66c258819c0..ea336944976ba9f8006365849357d9fa5bedc1be 100644
--- a/src/main/res/xml/header_preferences.xml
+++ b/src/main/res/xml/header_preferences.xml
@@ -1,9 +1,9 @@
-
-
-
-
-
+
+
+
+
+
\ No newline at end of file