@react-native-documents/picker 10.1.3 → 10.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3,6 +3,8 @@ package com.reactnativedocumentpicker
|
|
|
3
3
|
import android.content.ContentResolver
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.net.Uri
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.FileUtils
|
|
6
8
|
import com.facebook.react.bridge.Arguments
|
|
7
9
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
10
|
import com.facebook.react.bridge.ReactContext
|
|
@@ -18,7 +20,7 @@ import java.io.FileNotFoundException
|
|
|
18
20
|
import java.io.FileOutputStream
|
|
19
21
|
import java.io.IOException
|
|
20
22
|
import java.io.InputStream
|
|
21
|
-
import java.
|
|
23
|
+
import java.io.OutputStream
|
|
22
24
|
import java.util.UUID
|
|
23
25
|
|
|
24
26
|
class FileOperations(private val uriMap: MutableMap<String, Uri>) {
|
|
@@ -119,23 +121,17 @@ class FileOperations(private val uriMap: MutableMap<String, Uri>) {
|
|
|
119
121
|
val destFileSafe = safeGetDestination(attemptedDestFile, destinationDir)
|
|
120
122
|
|
|
121
123
|
val copyStreamToFile: (InputStream?) -> Unit = { inputStream ->
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
val inputChannel = Channels.newChannel(inputStream)
|
|
127
|
-
val size = destinationFileChannel.transferFrom(inputChannel, 0, Long.MAX_VALUE)
|
|
128
|
-
if (size == 0L) {
|
|
129
|
-
throw IOException("No data was copied to the destination file")
|
|
130
|
-
}
|
|
124
|
+
inputStream ?: throw FileNotFoundException("No input stream was found for the source file")
|
|
125
|
+
val bytesCopied = copyStreamToAnother(inputStream, FileOutputStream(destFileSafe))
|
|
126
|
+
if (bytesCopied == 0L) {
|
|
127
|
+
throw IOException("No data was copied to the destination file")
|
|
131
128
|
}
|
|
132
129
|
}
|
|
133
130
|
|
|
134
131
|
if (convertVirtualFileAsType == null) {
|
|
135
|
-
context.contentResolver.openInputStream(from)
|
|
132
|
+
copyStreamToFile(context.contentResolver.openInputStream(from))
|
|
136
133
|
} else {
|
|
137
|
-
getInputStreamForVirtualFile(context.contentResolver, from, convertVirtualFileAsType)
|
|
138
|
-
.use(copyStreamToFile)
|
|
134
|
+
copyStreamToFile(getInputStreamForVirtualFile(context.contentResolver, from, convertVirtualFileAsType))
|
|
139
135
|
}
|
|
140
136
|
|
|
141
137
|
return destFileSafe
|
|
@@ -161,9 +157,7 @@ class FileOperations(private val uriMap: MutableMap<String, Uri>) {
|
|
|
161
157
|
}
|
|
162
158
|
|
|
163
159
|
fun writeDocumentImpl(sourceUri: Uri?, targetUriString: String?, context: ReactApplicationContext): DocumentMetadataBuilder {
|
|
164
|
-
|
|
165
|
-
throw IllegalArgumentException("The source URI is null. Call saveDocument() before writeDocument()")
|
|
166
|
-
}
|
|
160
|
+
sourceUri ?: throw IllegalArgumentException("The source URI is null. Call saveDocument() before writeDocument()")
|
|
167
161
|
val targetUri: Uri? = uriMap[targetUriString]
|
|
168
162
|
|
|
169
163
|
if (targetUri == null) {
|
|
@@ -180,25 +174,30 @@ class FileOperations(private val uriMap: MutableMap<String, Uri>) {
|
|
|
180
174
|
val mimeFromUri = contentResolver.getType(targetUri)
|
|
181
175
|
metadataBuilder.mimeType(mimeFromUri)
|
|
182
176
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
val bytesCopied = inputStream.copyTo(outputStream)
|
|
193
|
-
if (bytesCopied == 0L) {
|
|
194
|
-
metadataBuilder.metadataReadingError("No data was copied to the destination file")
|
|
195
|
-
}
|
|
196
|
-
outputStream.flush()
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
177
|
+
val inputStream = contentResolver.openInputStream(sourceUri)
|
|
178
|
+
?: return metadataBuilder.metadataReadingError("No input stream found for source file")
|
|
179
|
+
|
|
180
|
+
val outputStream = contentResolver.openOutputStream(targetUri)
|
|
181
|
+
?: return metadataBuilder.metadataReadingError("No output stream found for destination file")
|
|
182
|
+
|
|
183
|
+
val bytesCopied = copyStreamToAnother(inputStream, outputStream)
|
|
184
|
+
if (bytesCopied == 0L) {
|
|
185
|
+
metadataBuilder.metadataReadingError("No data was copied to the destination file")
|
|
200
186
|
}
|
|
201
187
|
|
|
202
188
|
return metadataBuilder
|
|
203
189
|
}
|
|
190
|
+
|
|
191
|
+
val copyStreamToAnother: (InputStream, OutputStream) -> Long = { inputStream, outputStream ->
|
|
192
|
+
inputStream.use { input ->
|
|
193
|
+
outputStream.use { output ->
|
|
194
|
+
val bytesCopied = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
195
|
+
FileUtils.copy(inputStream, outputStream)
|
|
196
|
+
} else {
|
|
197
|
+
inputStream.copyTo(outputStream)
|
|
198
|
+
}
|
|
199
|
+
return@use bytesCopied
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
204
203
|
}
|
|
@@ -28,10 +28,10 @@ import MobileCoreServices
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
public func getMetadataFor(url: URL) throws -> DocumentMetadataBuilder {
|
|
31
|
-
if (currentOptions?.isOpenMode() == true) {
|
|
32
|
-
|
|
31
|
+
return if (currentOptions?.isOpenMode() == true) {
|
|
32
|
+
try self.getOpenedDocumentInfo(url: url, requestLongTermAccess: currentOptions?.requestLongTermAccess ?? false)
|
|
33
33
|
} else {
|
|
34
|
-
|
|
34
|
+
try self.getAnyModeMetadata(url: url)
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -4,45 +4,47 @@ import Foundation
|
|
|
4
4
|
|
|
5
5
|
@objc public class FileOperations: NSObject {
|
|
6
6
|
|
|
7
|
-
@objc public static func keepLocalCopyAtUniqueDestination(from:
|
|
8
|
-
|
|
9
|
-
let results =
|
|
7
|
+
@objc public static func keepLocalCopyAtUniqueDestination(from: [[String: String]], destinationPreset: String, resolve: @escaping RNDPPromiseResolveBlock) {
|
|
8
|
+
DispatchQueue.global(qos: .utility).async {
|
|
9
|
+
let results = moveFiles(from: from, destinationPreset: destinationPreset)
|
|
10
10
|
resolve(results)
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
static func moveFiles(from:
|
|
14
|
+
static func moveFiles(from: [[String: String]], destinationPreset: String) -> [[String: String?]] {
|
|
15
15
|
let destinationRootDir = getDirectoryForFileDestination(destinationPreset)
|
|
16
16
|
let uniqueSubDirName = UUID().uuidString
|
|
17
|
-
let destinationDir
|
|
18
|
-
// TODO do we need all of this Task dance?
|
|
17
|
+
let destinationDir = destinationRootDir.appendingPathComponent(uniqueSubDirName, isDirectory: true)
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
do {
|
|
26
|
-
guard let uriString = dictionary["uri"], let uri = URL(string: uriString) else {
|
|
27
|
-
return LocalCopyResponse.error(sourceUri: dictionary["uri"], copyError: "Invalid URI")
|
|
28
|
-
}
|
|
29
|
-
guard let fileName = dictionary["fileName"] else {
|
|
30
|
-
return LocalCopyResponse.error(sourceUri: uri.absoluteString, copyError: "Invalid fileName")
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let destinationUrl = try moveToDestination(from: uri, usingFilename: fileName, destinationDir: destinationDir)
|
|
34
|
-
return LocalCopyResponse.success(sourceUri: uri.absoluteString, localUri: destinationUrl.absoluteString)
|
|
35
|
-
} catch {
|
|
36
|
-
return LocalCopyResponse.error(sourceUri: dictionary["uri"]!, copyError: error.localizedDescription)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
19
|
+
do {
|
|
20
|
+
try FileManager.default.createDirectory(at: destinationDir, withIntermediateDirectories: true, attributes: nil)
|
|
21
|
+
} catch {
|
|
22
|
+
return from.map { dictionary in
|
|
23
|
+
LocalCopyResponse.error(sourceUri: dictionary["uri"], copyError: "Failed to create destination directory: \(error.localizedDescription)").dictionaryRepresentation
|
|
39
24
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// move files
|
|
28
|
+
return from.map { dictionary in
|
|
29
|
+
moveSingleFile(dictionary: dictionary, destinationDir: destinationDir).dictionaryRepresentation
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private static func moveSingleFile(dictionary: [String: String], destinationDir: URL) -> LocalCopyResponse {
|
|
34
|
+
guard let uriString = dictionary["uri"],
|
|
35
|
+
let uri = URL(string: uriString),
|
|
36
|
+
let fileName = dictionary["fileName"] else {
|
|
37
|
+
return LocalCopyResponse.error(
|
|
38
|
+
sourceUri: dictionary["uri"],
|
|
39
|
+
copyError: "Invalid URI or fileName"
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
do {
|
|
44
|
+
let destinationUrl = try moveToDestination(from: uri, usingFilename: fileName, destinationDir: destinationDir)
|
|
45
|
+
return LocalCopyResponse.success(sourceUri: uri.absoluteString, localUri: destinationUrl.absoluteString)
|
|
46
|
+
} catch {
|
|
47
|
+
return LocalCopyResponse.error(sourceUri: uriString, copyError: error.localizedDescription)
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
@@ -50,19 +52,20 @@ import Foundation
|
|
|
50
52
|
let destinationFile = destinationDir.appendingPathComponent(fileName).standardized
|
|
51
53
|
|
|
52
54
|
guard destinationFile.path.hasPrefix(destinationDir.standardized.path) else {
|
|
53
|
-
throw NSError(
|
|
55
|
+
throw NSError(
|
|
56
|
+
domain: "PathTraversalPrevention",
|
|
57
|
+
code: 400,
|
|
58
|
+
userInfo: [NSLocalizedDescriptionKey: "The copied file is attempting to write outside of the target directory."]
|
|
59
|
+
)
|
|
54
60
|
}
|
|
55
61
|
|
|
56
|
-
try FileManager.default.createDirectory(at: destinationDir, withIntermediateDirectories: true, attributes: nil)
|
|
57
62
|
try FileManager.default.moveItem(at: from, to: destinationFile)
|
|
58
63
|
|
|
59
64
|
return destinationFile
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
static func getDirectoryForFileDestination(_ copyToDirectory: String) -> URL {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
|
68
|
+
let searchPath: FileManager.SearchPathDirectory = copyToDirectory == "documentDirectory" ? .documentDirectory : .cachesDirectory
|
|
69
|
+
return FileManager.default.urls(for: searchPath, in: .userDomainMask).first!
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -28,14 +28,10 @@ import UniformTypeIdentifiers
|
|
|
28
28
|
|
|
29
29
|
static func createUTType(kind: String, value: String) -> UTType? {
|
|
30
30
|
switch kind {
|
|
31
|
-
case "UTType":
|
|
32
|
-
|
|
33
|
-
case "
|
|
34
|
-
|
|
35
|
-
case "extension":
|
|
36
|
-
return UTType(filenameExtension: value)
|
|
37
|
-
default:
|
|
38
|
-
return nil
|
|
31
|
+
case "UTType": UTType(value)
|
|
32
|
+
case "mimeType": UTType(mimeType: value)
|
|
33
|
+
case "extension": UTType(filenameExtension: value)
|
|
34
|
+
default: nil
|
|
39
35
|
}
|
|
40
36
|
}
|
|
41
37
|
}
|
package/package.json
CHANGED