diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java index 5f8f61948..791950733 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/SafXModule.java @@ -21,11 +21,13 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; +import java.util.regex.Pattern; @RequiresApi(api = VERSION_CODES.Q) @ReactModule(name = SafXModule.NAME) public class SafXModule extends ReactContextBaseJavaModule { public static final String NAME = "SafX"; + public static Pattern trailingSlash = Pattern.compile("[/\\\\]$"); private final DocumentHelper documentHelper; public SafXModule(ReactApplicationContext reactContext) { @@ -234,12 +236,16 @@ public class SafXModule extends ReactContextBaseJavaModule { DocumentFile doc = this.documentHelper.goToDocument(uriString, false, true); WritableMap[] resolvedDocs = - Arrays.stream(doc.listFiles()) - .map( - docEntry -> - DocumentHelper.resolveWithDocument( - docEntry, null, uriString + "/" + docEntry.getName())) - .toArray(WritableMap[]::new); + Arrays.stream(doc.listFiles()) + .map( + docEntry -> + DocumentHelper.resolveWithDocument( + docEntry, + null, + trailingSlash.matcher(uriString).replaceFirst("") + + "/" + + docEntry.getName())) + .toArray(WritableMap[]::new); WritableArray resolveData = Arguments.fromJavaArgs(resolvedDocs); promise.resolve(resolveData); } catch (FileNotFoundException e) { diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java index 23331d5cb..6f3fee7a0 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/DocumentHelper.java @@ -378,10 +378,37 @@ public class DocumentHelper { } public DocumentFile goToDocument( - String unknownUriString, boolean createIfDirectoryNotExist, boolean includeLastSegment) - throws SecurityException, IOException { + String unknownUriStr, boolean createIfDirectoryNotExist, boolean includeLastSegment) + throws SecurityException, IOException, IllegalArgumentException { + String unknownUriString = UriHelper.getUnifiedUri(unknownUriStr); if (unknownUriString.startsWith(ContentResolver.SCHEME_FILE)) { - return DocumentFile.fromFile(new File(Uri.parse(unknownUriString).getPath())); + Uri uri = Uri.parse(unknownUriString); + if (uri == null) { + throw new IllegalArgumentException("Invalid Uri String"); + } + String path = + uri.getPath() + .substring( + 0, + includeLastSegment + ? uri.getPath().length() + : uri.getPath().length() - uri.getLastPathSegment().length()); + + if (createIfDirectoryNotExist) { + File targetFile = new File(path); + if (!targetFile.exists()) { + boolean madeFolder = targetFile.mkdirs(); + if (!madeFolder) { + throw new IOException("mkdir failed for Uri with `file` scheme"); + } + } + } + DocumentFile targetFile = DocumentFile.fromFile(new File(path)); + if (!targetFile.exists()) { + throw new FileNotFoundException( + "Cannot find the given document. File does not exist at '" + unknownUriString + "'"); + } + return targetFile; } String uriString = UriHelper.normalize(unknownUriString); String baseUri = ""; @@ -459,7 +486,7 @@ public class DocumentHelper { public void transferFile( String srcUri, String destUri, boolean replaceIfDestExists, boolean copy, Promise promise) { try { - DocumentFile srcDoc = this.goToDocument(UriHelper.getUnifiedUri(srcUri), false, true); + DocumentFile srcDoc = this.goToDocument(srcUri, false, true); if (srcDoc.isDirectory()) { throw new IllegalArgumentException("Cannot move directories"); @@ -467,7 +494,7 @@ public class DocumentHelper { DocumentFile destDoc; try { - destDoc = this.goToDocument(UriHelper.getUnifiedUri(destUri), false, true); + destDoc = this.goToDocument(destUri, false, true); if (!replaceIfDestExists) { throw new IOException("a document with the same name already exists in destination"); } diff --git a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java index cae2a4aac..925c8aa72 100644 --- a/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java +++ b/packages/react-native-saf-x/android/src/main/java/com/reactnativesafx/utils/UriHelper.java @@ -15,36 +15,42 @@ public class UriHelper { } public static String normalize(String uriString) { - // an abnormal uri example: - // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin/locks/2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json - // normalized: - // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin%2Flocks%2F2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json + if (uriString.startsWith(ContentResolver.SCHEME_CONTENT)) { + // an abnormal uri example: + // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin/locks/2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json + // normalized: + // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin%2Flocks%2F2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json - // uri parts: - - String[] parts = Uri.decode(uriString).split(":"); - return parts[0] + ":" + parts[1] + Uri.encode(":" + parts[2]); + // uri parts: + String[] parts = Uri.decode(uriString).split(":"); + return parts[0] + ":" + parts[1] + Uri.encode(":" + parts[2]); + } + return uriString; } public static String denormalize(String uriString) { - // an normalized uri example: - // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin%2Flocks%2F2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json - // denormalized: - // content://com.android.externalstorage.documents/tree/1707-3F0B/Ajoplin/locks/2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json + if (uriString.startsWith(ContentResolver.SCHEME_CONTENT)) { + // an normalized uri example: + // content://com.android.externalstorage.documents/tree/1707-3F0B%3Ajoplin%2Flocks%2F2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json + // denormalized: + // content://com.android.externalstorage.documents/tree/1707-3F0B/Ajoplin/locks/2_2_fa4f9801e9a545a58f1a6c5d3a7cfded.json - return Uri.decode(normalize(uriString)); + return Uri.decode(normalize(uriString)); + } + return uriString; } - public static String getUnifiedUri(String uriString) throws Exception { + public static String getUnifiedUri(String uriString) throws IllegalArgumentException { Uri uri = Uri.parse(uriString); if (uri.getScheme() == null) { - uri = Uri.parse(ContentResolver.SCHEME_FILE+"://"+uriString); - } else if (!(uri.getScheme().equals(ContentResolver.SCHEME_FILE) || uri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { - throw new Exception("Scheme not supported"); + uri = Uri.parse(ContentResolver.SCHEME_FILE + "://" + uriString); + } else if (!(uri.getScheme().equals(ContentResolver.SCHEME_FILE) + || uri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) { + throw new IllegalArgumentException("Invalid Uri: Scheme not supported"); } if (uri.getScheme() == null) { - throw new Exception("Invalid Uri: Cannot determine scheme"); + throw new IllegalArgumentException("Invalid Uri: Cannot determine scheme"); } return uri.toString();