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