123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- package com.RNFetchBlob;
-
- import androidx.annotation.NonNull;
- import android.util.Base64;
-
- import com.facebook.react.bridge.Arguments;
- import com.facebook.react.bridge.ReactApplicationContext;
- import com.facebook.react.bridge.ReadableArray;
- import com.facebook.react.bridge.ReadableMap;
- import com.facebook.react.bridge.WritableMap;
- import com.facebook.react.modules.core.DeviceEventManagerModule;
-
- import java.io.ByteArrayInputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.ArrayList;
-
- import android.net.Uri;
- import okhttp3.MediaType;
- import okhttp3.RequestBody;
- import okio.BufferedSink;
-
- class RNFetchBlobBody extends RequestBody{
-
- private InputStream requestStream;
- private long contentLength = 0;
- private ReadableArray form;
- private String mTaskId;
- private String rawBody;
- private RNFetchBlobReq.RequestType requestType;
- private MediaType mime;
- private File bodyCache;
- int reported = 0;
- private Boolean chunkedEncoding = false;
-
- RNFetchBlobBody(String taskId) {
- this.mTaskId = taskId;
- }
-
- RNFetchBlobBody chunkedEncoding(boolean val) {
- this.chunkedEncoding = val;
- return this;
- }
-
- RNFetchBlobBody setMIME(MediaType mime) {
- this.mime = mime;
- return this;
- }
-
- RNFetchBlobBody setRequestType(RNFetchBlobReq.RequestType type) {
- this.requestType = type;
- return this;
- }
-
- /**
- * Set request body
- * @param body A string represents the request body
- * @return object itself
- */
- RNFetchBlobBody setBody(String body) {
- this.rawBody = body;
- if(rawBody == null) {
- this.rawBody = "";
- requestType = RNFetchBlobReq.RequestType.AsIs;
- }
- try {
- switch (requestType) {
- case SingleFile:
- requestStream = getRequestStream();
- contentLength = requestStream.available();
- break;
- case AsIs:
- contentLength = this.rawBody.getBytes().length;
- requestStream = new ByteArrayInputStream(this.rawBody.getBytes());
- break;
- case Others:
- break;
- }
- } catch(Exception ex) {
- ex.printStackTrace();
- RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create single content request body :" + ex.getLocalizedMessage() + "\r\n");
- }
- return this;
- }
-
- /**
- * Set request body (Array)
- * @param body A Readable array contains form data
- * @return object itself
- */
- RNFetchBlobBody setBody(ReadableArray body) {
- this.form = body;
- try {
- bodyCache = createMultipartBodyCache();
- requestStream = new FileInputStream(bodyCache);
- contentLength = bodyCache.length();
- } catch(Exception ex) {
- ex.printStackTrace();
- RNFetchBlobUtils.emitWarningEvent("RNFetchBlob failed to create request multipart body :" + ex.getLocalizedMessage());
- }
- return this;
- }
-
- @Override
- public long contentLength() {
- return chunkedEncoding ? -1 : contentLength;
- }
-
- @Override
- public MediaType contentType() {
- return mime;
- }
-
- @Override
- public void writeTo(@NonNull BufferedSink sink) {
- try {
- pipeStreamToSink(requestStream, sink);
- } catch(Exception ex) {
- RNFetchBlobUtils.emitWarningEvent(ex.getLocalizedMessage());
- ex.printStackTrace();
- }
- }
-
- boolean clearRequestBody() {
- try {
- if (bodyCache != null && bodyCache.exists()) {
- bodyCache.delete();
- }
- } catch(Exception e) {
- RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
- return false;
- }
- return true;
- }
-
- private InputStream getRequestStream() throws Exception {
-
- // upload from storage
- if (rawBody.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
- String orgPath = rawBody.substring(RNFetchBlobConst.FILE_PREFIX.length());
- orgPath = RNFetchBlobFS.normalizePath(orgPath);
- // upload file from assets
- if (RNFetchBlobFS.isAsset(orgPath)) {
- try {
- String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
- return RNFetchBlob.RCTContext.getAssets().open(assetName);
- } catch (Exception e) {
- throw new Exception("error when getting request stream from asset : " +e.getLocalizedMessage());
- }
- } else {
- File f = new File(RNFetchBlobFS.normalizePath(orgPath));
- try {
- if(!f.exists())
- f.createNewFile();
- return new FileInputStream(f);
- } catch (Exception e) {
- throw new Exception("error when getting request stream: " +e.getLocalizedMessage());
- }
- }
- } else if (rawBody.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
- String contentURI = rawBody.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
- try {
- return RNFetchBlob.RCTContext.getContentResolver().openInputStream(Uri.parse(contentURI));
- } catch (Exception e) {
- throw new Exception("error when getting request stream for content URI: " + contentURI, e);
- }
- }
- // base 64 encoded
- else {
- try {
- byte[] bytes = Base64.decode(rawBody, 0);
- return new ByteArrayInputStream(bytes);
- } catch(Exception ex) {
- throw new Exception("error when getting request stream: " + ex.getLocalizedMessage());
- }
- }
- }
-
- /**
- * Create a temp file that contains content of multipart form data content
- * @return The cache file object
- * @throws IOException
- */
- private File createMultipartBodyCache() throws IOException {
- String boundary = "RNFetchBlob-" + mTaskId;
-
- File outputDir = RNFetchBlob.RCTContext.getCacheDir(); // context being the Activity pointer
- File outputFile = File.createTempFile("rnfb-form-tmp", "", outputDir);
- FileOutputStream os = new FileOutputStream(outputFile);
-
- ArrayList<FormField> fields = countFormDataLength();
- ReactApplicationContext ctx = RNFetchBlob.RCTContext;
-
- for(FormField field : fields) {
- String data = field.data;
- String name = field.name;
- // skip invalid fields
- if(name == null || data == null)
- continue;
- // form begin
- String header = "--" + boundary + "\r\n";
- if (field.filename != null) {
- header += "Content-Disposition: form-data; name=\"" + name + "\"; filename=\"" + field.filename + "\"\r\n";
- header += "Content-Type: " + field.mime + "\r\n\r\n";
- os.write(header.getBytes());
- // file field header end
- // upload from storage
- if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
- String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
- orgPath = RNFetchBlobFS.normalizePath(orgPath);
- // path starts with content://
- if (RNFetchBlobFS.isAsset(orgPath)) {
- try {
- String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
- InputStream in = ctx.getAssets().open(assetName);
- pipeStreamToFileStream(in, os);
- } catch (IOException e) {
- RNFetchBlobUtils.emitWarningEvent("Failed to create form data asset :" + orgPath + ", " + e.getLocalizedMessage() );
- }
- }
- // data from normal files
- else {
- File file = new File(RNFetchBlobFS.normalizePath(orgPath));
- if(file.exists()) {
- FileInputStream fs = new FileInputStream(file);
- pipeStreamToFileStream(fs, os);
- }
- else {
- RNFetchBlobUtils.emitWarningEvent("Failed to create form data from path :" + orgPath + ", file not exists.");
- }
- }
- } else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
- String contentURI = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
- InputStream is = null;
- try {
- is = ctx.getContentResolver().openInputStream(Uri.parse(contentURI));
- pipeStreamToFileStream(is, os);
- } catch(Exception e) {
- RNFetchBlobUtils.emitWarningEvent(
- "Failed to create form data from content URI:" + contentURI + ", " + e.getLocalizedMessage());
- } finally {
- if (is != null) {
- is.close();
- }
- }
- }
- // base64 embedded file content
- else {
- byte[] b = Base64.decode(data, 0);
- os.write(b);
- }
-
- }
- // data field
- else {
- header += "Content-Disposition: form-data; name=\"" + name + "\"\r\n";
- header += "Content-Type: " + field.mime + "\r\n\r\n";
- os.write(header.getBytes());
- byte[] fieldData = field.data.getBytes();
- os.write(fieldData);
- }
- // form end
- os.write("\r\n".getBytes());
- }
- // close the form
- byte[] end = ("--" + boundary + "--\r\n").getBytes();
- os.write(end);
- os.flush();
- os.close();
- return outputFile;
- }
-
- /**
- * Pipe input stream to request body output stream
- * @param stream The input stream
- * @param sink The request body buffer sink
- * @throws IOException
- */
- private void pipeStreamToSink(InputStream stream, BufferedSink sink) throws IOException {
- byte[] chunk = new byte[10240];
- long totalWritten = 0;
- int read;
- while((read = stream.read(chunk, 0, 10240)) > 0) {
- sink.write(chunk, 0, read);
- totalWritten += read;
- emitUploadProgress(totalWritten);
- }
- stream.close();
- }
-
- /**
- * Pipe input stream to a file
- * @param is The input stream
- * @param os The output stream to a file
- * @throws IOException
- */
- private void pipeStreamToFileStream(InputStream is, FileOutputStream os) throws IOException {
-
- byte[] buf = new byte[10240];
- int len;
- while ((len = is.read(buf)) > 0) {
- os.write(buf, 0, len);
- }
- is.close();
- }
-
- /**
- * Compute approximate content length for form data
- * @return ArrayList<FormField>
- */
- private ArrayList<FormField> countFormDataLength() throws IOException {
- long total = 0;
- ArrayList<FormField> list = new ArrayList<>();
- ReactApplicationContext ctx = RNFetchBlob.RCTContext;
- for(int i = 0;i < form.size(); i++) {
- FormField field = new FormField(form.getMap(i));
- list.add(field);
- if(field.data == null) {
- RNFetchBlobUtils.emitWarningEvent("RNFetchBlob multipart request builder has found a field without `data` property, the field `"+ field.name +"` will be removed implicitly.");
- }
- else if (field.filename != null) {
- String data = field.data;
- // upload from storage
- if (data.startsWith(RNFetchBlobConst.FILE_PREFIX)) {
- String orgPath = data.substring(RNFetchBlobConst.FILE_PREFIX.length());
- orgPath = RNFetchBlobFS.normalizePath(orgPath);
- // path starts with asset://
- if (RNFetchBlobFS.isAsset(orgPath)) {
- try {
- String assetName = orgPath.replace(RNFetchBlobConst.FILE_PREFIX_BUNDLE_ASSET, "");
- long length = ctx.getAssets().open(assetName).available();
- total += length;
- } catch (IOException e) {
- RNFetchBlobUtils.emitWarningEvent(e.getLocalizedMessage());
- }
- }
- // general files
- else {
- File file = new File(RNFetchBlobFS.normalizePath(orgPath));
- total += file.length();
- }
- } else if (data.startsWith(RNFetchBlobConst.CONTENT_PREFIX)) {
- String contentURI = data.substring(RNFetchBlobConst.CONTENT_PREFIX.length());
- InputStream is = null;
- try {
- is = ctx.getContentResolver().openInputStream(Uri.parse(contentURI));
- long length = is.available();
- total += length;
- } catch(Exception e) {
- RNFetchBlobUtils.emitWarningEvent(
- "Failed to estimate form data length from content URI:" + contentURI + ", " + e.getLocalizedMessage());
- } finally {
- if (is != null) {
- is.close();
- }
- }
- }
- // base64 embedded file content
- else {
- byte[] bytes = Base64.decode(data, 0);
- total += bytes.length;
- }
- }
- // data field
- else {
- total += field.data.getBytes().length;
- }
- }
- contentLength = total;
- return list;
- }
-
- /**
- * Since ReadableMap could only be access once, we have to store the field into a map for
- * repeatedly access.
- */
- private class FormField {
- public String name;
- String filename;
- String mime;
- public String data;
-
- FormField(ReadableMap rawData) {
- if(rawData.hasKey("name"))
- name = rawData.getString("name");
- if(rawData.hasKey("filename"))
- filename = rawData.getString("filename");
- if(rawData.hasKey("type"))
- mime = rawData.getString("type");
- else {
- mime = filename == null ? "text/plain" : "application/octet-stream";
- }
- if(rawData.hasKey("data")) {
- data = rawData.getString("data");
- }
- }
- }
-
- /**
- * Emit progress event
- * @param written Integer
- */
- private void emitUploadProgress(long written) {
- RNFetchBlobProgressConfig config = RNFetchBlobReq.getReportUploadProgress(mTaskId);
- if(config != null && contentLength != 0 && config.shouldReport((float)written/contentLength)) {
- WritableMap args = Arguments.createMap();
- args.putString("taskId", mTaskId);
- args.putString("written", String.valueOf(written));
- args.putString("total", String.valueOf(contentLength));
-
- // emit event to js context
- RNFetchBlob.RCTContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
- .emit(RNFetchBlobConst.EVENT_UPLOAD_PROGRESS, args);
- }
- }
-
- }
|