Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scoped storage fixes and improvements #205

Merged
merged 13 commits into from
Jul 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ private void tryToUpgradeToScopedStorage(Uri treeUri) {
if (!Util.useScopedStorage()) {
DocumentFile df = FileUtil.getDocumentFileFromUri(treeUri);
if (df == null) return;
final String a11Path = FileUtil.getUriStoragePathFullFromDocumentFile(df, "");
final String a11Path = FileUtil.getFileTypePathFromDocumentFile(df);
if (a11Path == null) return;
File root = new File(a11Path);
if (!root.canRead() || !root.canWrite()) {
Expand All @@ -370,7 +370,7 @@ private void scopedStorageChrootOverride(Uri treeUri) {
if (Util.useScopedStorage()) {
DocumentFile df = FileUtil.getDocumentFileFromUri(treeUri);
if (df == null) return;
final String scopedStoragePath = FileUtil.getUriStoragePathFullFromDocumentFile(df, "");
final String scopedStoragePath = FileUtil.getFileTypePathFromDocumentFile(df);
if (scopedStoragePath == null) return;
List<FtpUser> userList = FsSettings.getUsers();
for (int i = 0; i < userList.size(); i++) {
Expand Down
42 changes: 28 additions & 14 deletions app/src/main/java/be/ppareit/swiftp/server/CmdAbstractStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,21 +85,32 @@ public void doStorOrAppe(String param, boolean append) {
}
}

if (!storeFile.exists()) {
if (Util.useScopedStorage()) {
final String mime = "application/octet-stream";
//final URI fileUri = storeFile.toURI();
//final URL url = fileUri.toURL();
//mime = url.openConnection().getContentType(); // sometimes makes exe's into html files
docStoreFile = FileUtil.mkfile(storeFile, App.getAppContext(), mime);
if(docStoreFile == null){
errString = "451 Couldn't open file \"" + param + "\" aka \""
+ storeFile.getCanonicalPath() + "\" for writing\r\n";
if (Util.useScopedStorage()) {
final String mime = "application/octet-stream";
// Fix: REST needs check of exists here. sessionThread.offset must be checked
// as correct before using it as it can be wrong during connection issues. As
// it is not at this time, do not use it here.
if (!append && !storeFile.exists()) {
if (storeFile.exists()) {
errString = "451 Couldn't truncate file\r\n";
break storing;
}
docStoreFile = FileUtil.mkfile(storeFile, mime);
} else {
FileUtil.mkfile(storeFile, App.getAppContext());
docStoreFile = FileUtil.getDocumentFile(storeFile.getPath());
}
if (docStoreFile == null || !docStoreFile.exists()) {
errString = "451 Couldn't open file \"" + param + "\" aka \""
+ storeFile.getCanonicalPath() + "\" for writing\r\n";
break storing;
}
// Fix: Do not allow this situation to pass.
if (sessionThread.offset > 0 && docStoreFile.length() == 0) {
errString = "554 restart failure: bad offset\r\n";
break storing;
}
} else {
FileUtil.mkfile(storeFile, App.getAppContext());
}

if (docStoreFile != null) {
Expand Down Expand Up @@ -200,14 +211,17 @@ public void doStorOrAppe(String param, boolean append) {
}
}
try {
if (out != null) {
out.close();
}
if (os != null) {
os.close();
}
} catch (IOException ignored) {
}
try {
if (out != null) {
out.close();
}
} catch (IOException ignored) {
}

if (errString != null) {
Cat.i("STOR error: " + errString.trim());
Expand Down
25 changes: 8 additions & 17 deletions app/src/main/java/be/ppareit/swiftp/server/CmdDELE.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@

package be.ppareit.swiftp.server;

import java.io.File;

import android.util.Log;

import androidx.documentfile.provider.DocumentFile;

import java.io.File;

import be.ppareit.swiftp.App;
import be.ppareit.swiftp.MediaUpdater;
import be.ppareit.swiftp.Util;
import be.ppareit.swiftp.utils.FileUtil;
import be.ppareit.swiftp.MediaUpdater;

public class CmdDELE extends FtpCmd implements Runnable {
private static final String TAG = CmdDELE.class.getSimpleName();
Expand All @@ -48,31 +48,22 @@ public void run() {
sessionThread.getWorkingDir(), param);

if (Util.useScopedStorage()) {
String clientPath;
final String sfPath = storeFile.getPath();
if (sfPath.contains(File.separator)) {
clientPath = sfPath.substring(0, sfPath.lastIndexOf(File.separator));
} else {
clientPath = sfPath;
}
DocumentFile docStoreFile = FileUtil.getDocumentFileWithParamScopedStorage(File.separator +
param, null, clientPath);
tryToDelete(new FileUtil.Gen(docStoreFile), clientPath);
DocumentFile docStoreFile = FileUtil.getDocumentFile(storeFile.getPath());
tryToDelete(new FileUtil.Gen(docStoreFile));
return;
}

tryToDelete(new FileUtil.Gen(storeFile), param);
tryToDelete(new FileUtil.Gen(storeFile));
}

private void tryToDelete(FileUtil.Gen storeFile, String param) {
private void tryToDelete(FileUtil.Gen storeFile) {
String errString = null;
if (storeFile == null || storeFile.getOb() == null) {
errString = "550 Invalid name or chroot violation\r\n";
} else {
final boolean isDocumentFile = storeFile.getOb() instanceof DocumentFile;
final boolean isFile = !isDocumentFile;
final String path = FileUtil.getScopedClientPath(param, null, null);
if ((isDocumentFile && violatesChroot((DocumentFile) storeFile.getOb(), path))
if ((isDocumentFile && violatesChroot((DocumentFile) storeFile.getOb()))
|| (isFile && violatesChroot((File) storeFile.getOb()))) {
errString = "550 Invalid name or chroot violation\r\n";
} else if (storeFile.isDirectory()) {
Expand Down
45 changes: 32 additions & 13 deletions app/src/main/java/be/ppareit/swiftp/server/CmdHASH.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package be.ppareit.swiftp.server;

import androidx.documentfile.provider.DocumentFile;

import net.vrallev.android.cat.Cat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import be.ppareit.swiftp.App;
import be.ppareit.swiftp.utils.FileUtil;


/**
* CmdHASH provides a method to verify the integrity of a transferred file or to compare two files
Expand All @@ -34,45 +40,55 @@ public void run() {
{
fileToHash = inputPathToChrootedFile(sessionThread.getChrootDir(),
sessionThread.getWorkingDir(), param);
if (violatesChroot(fileToHash)) {

FileUtil.Gen gen = FileUtil.createGenFromFile(fileToHash);
final boolean isDocumentFile = gen.getOb() instanceof DocumentFile;
final boolean isFile = !isDocumentFile;

if (isFile && violatesChroot((File) gen.getOb())
|| isDocumentFile && violatesChroot((DocumentFile) gen.getOb())) {
errString = "550 Invalid name or chroot violation\r\n";
break mainblock;
} else if (fileToHash.isDirectory()) {
} else if (gen.isDirectory()) {
Cat.d("Ignoring HASH for directory");
errString = "553 Can't HASH a directory\r\n";
break mainblock;
} else if (!fileToHash.exists()) {
Cat.d("Can't HASH nonexistent file: " + fileToHash.getAbsolutePath());
} else if (!gen.exists()) {
Cat.d("Can't HASH nonexistent file: " + gen.getAbsolutePath());
errString = "550 File does not exist\r\n";
break mainblock;
} else if (!fileToHash.canRead()) {
} else if (!gen.canRead()) {
Cat.i("Failed HASH permission (canRead() is false)");
errString = "556 No read permissions\r\n";
break mainblock;
}

FileInputStream in = null;
InputStream is = null;
try {
String algorithm = sessionThread.getHashingAlgorithm();
MessageDigest md = MessageDigest.getInstance(algorithm);
byte[] buffer = new byte[SessionThread.DATA_CHUNK_SIZE];
in = new FileInputStream(fileToHash);
if (isFile) in = new FileInputStream((File) gen.getOb());
else
is = App.getAppContext().getContentResolver().openInputStream(((DocumentFile) gen.getOb()).getUri());

long offset = 0L;
long endPosition = fileToHash.length() - 1;
long endPosition = gen.length() - 1;
if (sessionThread.offset >= 0) {
offset = sessionThread.offset;
if (offset <= sessionThread.endPosition
&& sessionThread.endPosition <= fileToHash.length() - 1) {
&& sessionThread.endPosition <= gen.length() - 1) {
endPosition = sessionThread.endPosition;
}
}

// This is not a range but length (Range 0-0 would still read 0th byte), so +1
long bytesToRead = endPosition - offset + 1;
int bytesRead;
in.skip(offset);
while ((bytesRead = in.read(buffer)) != -1) {
if (isFile) in.skip(offset);
else is.skip(offset);
while ((bytesRead = isFile ? in.read(buffer) : is.read(buffer)) != -1) {
if (bytesRead > bytesToRead) {
md.update(buffer, 0, (int) bytesToRead);
break;
Expand Down Expand Up @@ -101,8 +117,11 @@ public void run() {
break mainblock;
} finally {
try {
if (in != null)
in.close();
if (in != null) in.close();
} catch (IOException ignore) {
}
try {
if (is != null) is.close();
} catch (IOException ignore) {
}
}
Expand All @@ -112,4 +131,4 @@ public void run() {
}
Cat.d("HASH done");
}
}
}
17 changes: 6 additions & 11 deletions app/src/main/java/be/ppareit/swiftp/server/CmdLIST.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,15 @@

package be.ppareit.swiftp.server;

import android.net.Uri;

import androidx.documentfile.provider.DocumentFile;

import net.vrallev.android.cat.Cat;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import androidx.documentfile.provider.DocumentFile;

import net.vrallev.android.cat.Cat;

import be.ppareit.swiftp.Util;
import be.ppareit.swiftp.utils.FileUtil;

Expand Down Expand Up @@ -68,9 +66,7 @@ public void run() {
if (param.equals("")) {
fileToList = sessionThread.getWorkingDir();
if (Util.useScopedStorage()) {
final String clientPath = fileToList.getPath();
Uri uri = FileUtil.getFullCWDUri("", clientPath);
docFileToList = FileUtil.getDocumentFileFromUri(uri);
docFileToList = FileUtil.getDocumentFile(fileToList.getPath());
}
} else {
if (param.contains("*")) {
Expand All @@ -79,8 +75,7 @@ public void run() {
}
fileToList = new File(sessionThread.getWorkingDir(), param);
if (Util.useScopedStorage()) {
final String clientPath = fileToList.getPath();
docFileToList = FileUtil.getDocumentFileWithParamScopedStorage(param, "", clientPath);
docFileToList = FileUtil.getDocumentFile(fileToList.getPath());
}
if (violatesChroot(fileToList)) {
// sd card should be eg /storage/xxx/
Expand Down
30 changes: 10 additions & 20 deletions app/src/main/java/be/ppareit/swiftp/server/CmdRETR.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,15 @@ public void run() {

DocumentFile docFileToRetr = null;
if (Util.useScopedStorage()) {
String clientPath;
final String ftrPath = fileToRetr.getPath();
if (ftrPath.contains(File.separator)) {
clientPath = ftrPath.substring(0, ftrPath.lastIndexOf(File.separator));
} else {
clientPath = ftrPath;
}
docFileToRetr = FileUtil.getDocumentFileWithParamScopedStorage(param, null, clientPath);
docFileToRetr = FileUtil.getDocumentFile(fileToRetr.getPath());
if (docFileToRetr == null) {
errString = "550 File does not exist\r\n";
break mainblock;
}
if (!docFileToRetr.exists()) {
errString = "550 File does not exist\r\n";
break mainblock;
}
}

FileUtil.Gen gen;
Expand Down Expand Up @@ -114,7 +111,8 @@ public void run() {
long bytesToRead = endPosition - offset + 1;
if (Util.useScopedStorage()) is.skip(offset);
else in.skip(offset);
while ((bytesRead = (Util.useScopedStorage() ? is.read(buffer) : in.read(buffer))) != -1) {
final boolean scoped = Util.useScopedStorage();
while ((bytesRead = (scoped ? is.read(buffer) : in.read(buffer))) != -1) {
boolean success;
if (bytesRead > bytesToRead) {
success = sessionThread.sendViaDataSocket(buffer, 0, (int) bytesToRead);
Expand All @@ -138,7 +136,8 @@ public void run() {
}
// We have to convert all solitary \n to \r\n
boolean lastBufEndedWithCR = false;
while ((bytesRead = (Util.useScopedStorage() ? is.read(buffer) : in.read(buffer))) != -1) {
final boolean scoped = Util.useScopedStorage();
while ((bytesRead = (scoped ? is.read(buffer) : in.read(buffer))) != -1) {
int startPos = 0, endPos = 0;
byte[] crnBuf = {'\r', '\n'};
for (endPos = 0; endPos < bytesRead; endPos++) {
Expand Down Expand Up @@ -207,16 +206,7 @@ private String validate(FileUtil.Gen fileToRetr, String param) {
final boolean isDocumentFile = fileToRetr.getOb() instanceof DocumentFile;
final boolean isFile = !isDocumentFile;

String clientPath;
String path = null;
if (isDocumentFile) {
clientPath = param;
if (!param.contains(File.separator)) clientPath = "";
else if (!param.endsWith(File.separator)) clientPath = param.substring(0, param
.lastIndexOf(File.separator));
path = FileUtil.getScopedClientPath(clientPath, null, null);
}
if ((isDocumentFile && violatesChroot((DocumentFile) fileToRetr.getOb(), path))
if ((isDocumentFile && violatesChroot((DocumentFile) fileToRetr.getOb()))
|| (isFile && violatesChroot((File) fileToRetr.getOb()))) {
errString = "550 Invalid name or chroot violation\r\n";
} else if (fileToRetr.isDirectory()) {
Expand Down
Loading