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

Add blob support #581

Merged
merged 5 commits into from Oct 26, 2021
Merged

Add blob support #581

merged 5 commits into from Oct 26, 2021

Conversation

tomekzaw
Copy link
Contributor

@tomekzaw tomekzaw commented Jul 8, 2021

This pull request adds support for loading PDF documents from original React Native's Blob via object URL.

An example use case of this feature is decrypting PDF documents and storing the decrypted data in memory as a Blob instead of saving the file on the device's filesystem, which may be not desirable due to security reasons.

Example

<Pdf source={{uri: URL.createObjectURL(blob)}} />

Sample blob object URLs:

  • Android: content://com.example.blobs/48fa2c0e-50ad-437c-a60a-5920292cb850?offset=0&size=22092
  • iOS: blob:F9B2C33F-55E7-4831-A0A7-EB95CC6F8DFF?offset=0&size=22092

Note: On Android, it is necessary to register BlobProvider as a ContentProvider in order to create object URLs for blobs (see details). On iOS, there is no configuration required.

Current behaviour

Android

Passing a content URI as <Pdf /> component source on Android causes the following error: [Error: File is empty].

This exception is thrown in native implementation of nativeOpenDocument method in PdfiumAndroid (see the code). On Android, BlobProvider returns the file descriptor to read side of a pipe. PdfiumAndroid calls fstat to check the file size (see the code). For a pipe, st_size is zero (see https://yarchive.net/comp/linux/stat_size.html).

This issue has been reproduced using test.pdf document which is smaller than 64 KB due to an issue with BlobProvider when accessing blobs larger than pipe capacity (see issue facebook/react-native#31774 and pull request facebook/react-native#31789). This will be fixed in React Native 0.65.0.

iOS

Passing a blob URL as <Pdf /> component source causes the following error: [Error: Load pdf failed. path=blob:1B035D9C-B720-48F9-B79D-8648B6153D35?offset=0&size=22092].

_onChange(@{ @"message": [[NSString alloc] initWithString:[NSString stringWithFormat:@"error|Load pdf failed. path=%s",_path.UTF8String]]});

Instantiating PDFDocument from PDFKit using initWithURL method fails, because the passed blob URL is not a valid file URL to an existing file, thus _pdfDocument is Nil.

_pdfDocument = [[PDFDocument alloc] initWithURL:fileURL];

Changes

Android

The new implementation uses ContentResolver.openInputStream method to resolve blob from the provided content URI and then passes the input stream directly to Configurator.fromStream method. This happens only if the provided URL is prefixed with content://.

iOS

Because there is no content provider mechanism on iOS, the new implementation simply accesses the BlobModule and retrieves the blob data using resolveURL method. The NSURL object is instantiated using URLWithString method instead of fileURLWithPath. This happens only if the provided URL is prefixed with blob:.

Further improvements

Android

While the suggested approach is to make use of ContentProvider mechanism (which is similar to what React Native's ImageView does), it has two major drawbacks. First, it will not work properly on older versions of React Native (without facebook/react-native#31789 fix) as the vast majority of PDFs is larger than 64 KB. Another drawback is that native code cannot use Java streams, so the input stream from ContentProvider is actually copied to a new bytearray (see here, here and here).

Instead, we could get blob data directly from BlobProvider using resolve method (just like here), analogously as for iOS, and then call Configurator.fromBytes method. This approach would eliminate the need for copying the bytearray (see here, here and here).

byte[] data = blobModule.resolve(uri);
configurator = this.fromBytes(data);

Additionally, this approach does not require patching React Native as it completely avoids BlobProvider.

Another minor improvement would be to use BLOB_URI_SCHEME (from here) instead of hard-coding the content:// prefix.

iOS

Using BLOB_URI_SCHEME from here instead of the hard-coded blob: prefix would be a minor improvement.

Testing

Due to issue with blobs larger than 64 KB on Android, the example app has been tested with test.pdf document fetched as blob from Python HTTP server which can be started with the following command:

python3 -m http.server
const ip = Platform.OS === "android" ? "10.0.2.2" : "127.0.0.1";
const url = `http://{ip}:8000/test.pdf`;
Android iOS
Android iOS

@wonday wonday merged commit 9a23a7a into wonday:master Oct 26, 2021
@wonday
Copy link
Owner

wonday commented Oct 26, 2021

Thank you.

@tomekzaw tomekzaw deleted the blob-support branch March 12, 2022 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants