birdofpreyru / react-native-fs

File system access for React Native
https://dr.pogodin.studio/docs/react-native-file-system
Other
160 stars 12 forks source link

implement security scoped url with bookmark for readDir on iOS #52

Closed zenoxs closed 3 months ago

zenoxs commented 3 months ago

Implement security scoped url with bookmark for readDir on iOS (more info: https://developer.apple.com/documentation/uikit/view_controllers/providing_access_to_directories)

The function now accept a bookmark:// scheme following by the iOS bookmark in base64.

Example of usage with the @react-native-documents/picker librairie to pick up the bookmark of a folder on iOS:

import * as RNFS from '@dr.pogodin/react-native-fs'
import { pickDirectory } from '@react-native-documents/picker'

const res = await pickDirectory({
  requestLongTermAccess: true // Request the bookmark 
})

if (res.bookmarkStatus === 'success') {
  const files = await RNFS.readDir(`bookmark://${res.bookmark}`)
  console.log(files)
}

NB: I was not sure on how to handle the exception for readDir function, feel free to edit my PR or add comments :)

Related to: https://github.com/birdofpreyru/react-native-fs/issues/51

birdofpreyru commented 3 months ago

Looks fine. Will double-check it does not break tests in the Example App and release shortly. Are you using a 3rd part library to get the bookmark because pickFile() from this lib only allows to pick files? Isn't it possible to ask it to select folders, by some special mimeType option value? If so, I'd create a ticket that we should support selecting folders by the system file-picker in this lib.

birdofpreyru commented 3 months ago

Well... it turned out your PR even did not compile; and when looking into it closely I figured out that readDir() function had to be further refactored to use NSURL objects all around, rather than relying on old logic using path strings (I doubt the old way would work for security scoped stuff).

zenoxs commented 3 months ago

Looks fine. Will double-check it does not break tests in the Example App and release shortly. Are you using a 3rd part library to get the bookmark because pickFile() from this lib only allows to pick files? Isn't it possible to ask it to select folders, by some special mimeType option value? If so, I'd create a ticket that we should support selecting folders by the system file-picker in this lib.

Sadly, I am using the premium version of this lib. There is actually no free react native lib that allows selecting a folder. I think it's not that hard to implement, but it would probably easier to do in swift rather than objectif c.

EDIT: Nvm, it looks like you can simply edit the the pickFile method on ReactNativeFs.mm to forward this argument ´initWithDocumentTypes:@[(NSString *)kUTTypeFolder]´ in order to request a directory picker, this would give the following code :

        picker = [[UIDocumentPickerViewController alloc]
                  // Add the following line
                  initWithDocumentTypes:@[(NSString *)kUTTypeFolder]
                  inMode:UIDocumentPickerModeOpen];

then on the App.tsx of the example:

            const res = await pickFile();
            if (res.length > 0 && res[0]) {
              const items = await readDir(res[0]);
              console.log(items);
            }

I tried on my own and it returns a correct bookmark for the picked directory, and I can then list its content with the readDir method.

zenoxs commented 3 months ago

Well... it turned out your PR even did not compile; and when looking into it closely I figured out that readDir() function had to be further refactored to use NSURL objects all around, rather than relying on old logic using path strings (I doubt the old way would work for security scoped stuff).

Sorry for that, that's really weird. I tested my branch on simulator and real device. I also patched the lib with the same code on my application and everything works as expected. Could you describe what error you got on compilation ? And which version of xcode you are using ?

birdofpreyru commented 3 months ago

Could you describe what error you got on compilation?

You did the following, and NSURL does not have stringByAppendingPathComponent method.

NSString *path = [dirUrl stringByAppendingPathComponent:obj];

Then, even it has other means to turn an NSURL into a NSString URL representation, I guess doing so you loose any security permissions granted to the original NSURL instance. Have a look how I ended up doing it — only using NSURL methods to get child URLs and retrieve their attributes.

EDIT: Nvm, it looks like you can simply edit the the pickFile method on ReactNativeFs.mm to forward this argument

Yeah, that is about what I thought. Actually, there might be no need to edit anything, see how pickFile() transforms mimeTypes option it gets into the corresponding UTType (in particular, the line 1218):

https://github.com/birdofpreyru/react-native-fs/blob/ef41f0b4f52d3ebe79dbb19c57a9ef3f3e48b170/ios/ReactNativeFs.mm#L1214-L1220

I guess, there might be a MIME string (to be checked in iOS documentation) denoting a directory, which you can pass in on TS side, to automatically get kUTTypeFolder type on the iOS side. And if there is no such string, we should introduce one, and handle it similar to how */* MIME value is handled there. Also... one way or another, it should be ensured to work the same for Android, and Windows; and library README should mention it (and perhaps even expose an auxiliary pickFolder() method).