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

feat: CapacitorHttp - Support multipart/form-data (FormData) #6142

Closed
millerg6711 opened this issue Dec 4, 2022 · 5 comments
Closed

feat: CapacitorHttp - Support multipart/form-data (FormData) #6142

millerg6711 opened this issue Dec 4, 2022 · 5 comments
Labels
needs reproduction needs reproducible example to illustrate the issue platform: android platform: ios

Comments

@millerg6711
Copy link

Feature Request

Description

Support multipart/form-data (FormData) in CapacitorHttp Plugin for iOS, and Android. It's currently sending as an empty object.

Platform(s)

  • iOS
  • Android

Preferred Solution

Please use similar implementation as Cordova Plugin Advanced Http

function processFormData(data, cb) {
    dependencyValidator.checkBlobApi();
    dependencyValidator.checkFileReaderApi();
    dependencyValidator.checkTextEncoderApi();
    dependencyValidator.checkFormDataInstance(data);

    var textEncoder = new global.TextEncoder('utf8');
    var iterator = data.entries();

    var result = {
      buffers: [],
      names: [],
      fileNames: [],
      types: []
    };

    processFormDataIterator(iterator, textEncoder, result, cb);
  }
  function processFormDataIterator(iterator, textEncoder, result, onFinished) {
    var entry = iterator.next();

    if (entry.done) {
      return onFinished(result);
    }

    if (entry.value[1] instanceof global.Blob || entry.value[1] instanceof global.File) {
      var reader = new global.FileReader();

      reader.onload = function () {
        result.buffers.push(base64.fromArrayBuffer(reader.result));
        result.names.push(entry.value[0]);
        result.fileNames.push(entry.value[1].name !== undefined ? entry.value[1].name : 'blob');
        result.types.push(entry.value[1].type || '');
        processFormDataIterator(iterator, textEncoder, result, onFinished);
      };

      return reader.readAsArrayBuffer(entry.value[1]);
    }

    if (jsUtil.getTypeOf(entry.value[1]) === 'String') {
      result.buffers.push(base64.fromArrayBuffer(textEncoder.encode(entry.value[1]).buffer));
      result.names.push(entry.value[0]);
      result.fileNames.push(null);
      result.types.push('text/plain');

      return processFormDataIterator(iterator, textEncoder, result, onFinished)
    }

    // skip items which are not supported
    processFormDataIterator(iterator, textEncoder, result, onFinished);
  }

Android Implementation

protected void sendBody(HttpRequest request) throws Exception {
    // .... 
    if ("multipart".equals(this.serializer)) {
      JSONArray buffers = ((JSONObject) this.data).getJSONArray("buffers");
      JSONArray names = ((JSONObject) this.data).getJSONArray("names");
      JSONArray fileNames = ((JSONObject) this.data).getJSONArray("fileNames");
      JSONArray types = ((JSONObject) this.data).getJSONArray("types");

      for (int i = 0; i < buffers.length(); ++i) {
        byte[] bytes = Base64.decode(buffers.getString(i), Base64.DEFAULT);
        String name = names.getString(i);

        if (fileNames.isNull(i)) {
          request.part(name, new String(bytes, "UTF-8"));
        } else {
          request.part(name, fileNames.getString(i), types.getString(i), new ByteArrayInputStream(bytes));
        }
      }

      // prevent sending malformed empty multipart requests (#372)
      if (buffers.length() == 0) {
        request.contentType("multipart/form-data; boundary=00content0boundary00");
        request.send("\r\n--00content0boundary00--\r\n");
      }
    }
  }

iOS Implementation

void (^constructBody)(id<AFMultipartFormData>) = ^(id<AFMultipartFormData> formData) {
    NSArray *buffers = [data mutableArrayValueForKey:@"buffers"];
    NSArray *fileNames = [data mutableArrayValueForKey:@"fileNames"];
    NSArray *names = [data mutableArrayValueForKey:@"names"];
    NSArray *types = [data mutableArrayValueForKey:@"types"];

    NSError *error;

    for (int i = 0; i < [buffers count]; ++i) {
        NSData *decodedBuffer = [[NSData alloc] initWithBase64EncodedString:[buffers objectAtIndex:i] options:0];
        NSString *fileName = [fileNames objectAtIndex:i];
        NSString *partName = [names objectAtIndex:i];
        NSString *partType = [types objectAtIndex:i];

        if (![fileName isEqual:[NSNull null]]) {
            [formData appendPartWithFileData:decodedBuffer name:partName fileName:fileName mimeType:partType];
        } else {
            [formData appendPartWithFormData:decodedBuffer name:[names objectAtIndex:i]];
        }
    }

    if (error) {
        [weakSelf removeRequest:reqId];

        NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
        [dictionary setObject:[NSNumber numberWithInt:400] forKey:@"status"];
        [dictionary setObject:@"Could not add part to multipart request body." forKey:@"error"];
        CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsDictionary:dictionary];
        [weakSelf.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
        [[SDNetworkActivityIndicator sharedActivityIndicator] stopActivity];
        return;
    }
};

Additional Context

FormData Request
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#uploading_a_file

Cordova Plugin Advanced Http
https://github.com/silkimen/cordova-plugin-advanced-http/blob/87c915f7c0938006e3d88f7f06a92e9fe35e7f65/www/helpers.js

@ionitron-bot ionitron-bot bot added the triage label Dec 4, 2022
@ionitron-bot ionitron-bot bot removed the triage label Dec 4, 2022
@millerg6711 millerg6711 changed the title feat: Support multipart/form-data (FormData) in CapacitorHttp Plugin feat: CapacitorHttp - Support multipart/form-data (FormData) Dec 4, 2022
@jcesarmobile jcesarmobile added the needs reproduction needs reproducible example to illustrate the issue label Dec 5, 2022
@Ionitron
Copy link
Collaborator

Ionitron commented Dec 5, 2022

This issue may need more information before it can be addressed. In particular, it will need a reliable Code Reproduction that demonstrates the issue.

Please see the Contributing Guide for how to create a Code Reproduction.

Thanks!
Ionitron 💙

@Ionitron Ionitron added the needs reply needs reply from the user label Dec 5, 2022
@muuvmuuv

This comment was marked as off-topic.

@Ionitron
Copy link
Collaborator

It looks like this issue didn't get the information it needed, so I'll close it for now. If I made a mistake, sorry! I am just a bot.

Have a great day!
Ionitron 💙

@Ionitron Ionitron removed the needs reply needs reply from the user label Dec 31, 2022
@muuvmuuv
Copy link

This is still mandatory

@ionitron-bot
Copy link

ionitron-bot bot commented Jan 30, 2023

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Capacitor, please create a new issue and ensure the template is fully filled out.

@ionitron-bot ionitron-bot bot locked and limited conversation to collaborators Jan 30, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
needs reproduction needs reproducible example to illustrate the issue platform: android platform: ios
Projects
None yet
Development

No branches or pull requests

4 participants