Search code examples
node.jsencodingzipmultipartform-dataslack

How to encode zip files as part of multipart/form-data


tldr I'm having encoding issues when sending zip files as part of a multipart/form-data request body. Help please :/

I'm working on a bot that should be able to upload zip files to Slack (via their file api), but I'm running into some issues that I believe are related to encoding.

So, I'm creating my request body as follows:

var form_string = "\n--abcdefghijklmnop\nContent-Disposition: form-data; filename=\"" + filename + "\"; name=\"file\";\nContent-Type:application/octet-stream;\nContent-Transfer-Encoding:base64;\n\n" + data;
form_string += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"token\";\n\n" + token;
form_string += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"filetype\";\n\n" + filetype;
form_string += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"filename\";\n\n" + filename;
form_string += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"channels\";\n\n" + channel;
form_string += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"title\";\n\n" + title;
form_string += "\n--abcdefghijklmnop--";

var form = Buffer.from(form_string, "utf8");

var headers = {
  "Content-Type": "multipart/form-data; boundary=abcdefghijklmnop",
  "Content-Length": form.length,
  "Authorization": "Bearer ....."
};
var options = {
  "headers": headers,
  "body": form 
};
// using the sync-request node module. 
var res = request("POST", url, options);

var res = request("POST", url, options);

(I've tried application/zip and application/x-zip-compressed as well. I've also tried both binary and base64 content transfer encodings.)

(And in case you're wondering, I need to make synchronous http requests...)

I created a really small zip file as a test. The base64 encoding of it is below:

UEsDBAoAAAAAAAqR+UoAAAAAAAAAAAAAAAAIABwAdGlueXppcC9VVAkAA1PBd1mDwXdZdXgLAAEE9QEAAAQUAAAAUEsDBAoAAAAAAAuR+Up6em/tAwAAAAMAAAAQABwAdGlueXppcC90aW55LnR4dFVUCQADVsF3WVzBd1l1eAsAAQT1AQAABBQAAABoaQpQSwECHgMKAAAAAAAKkflKAAAAAAAAAAAAAAAACAAYAAAAAAAAABAA7UEAAAAAdGlueXppcC9VVAUAA1PBd1l1eAsAAQT1AQAABBQAAABQSwECHgMKAAAAAAALkflKenpv7QMAAAADAAAAEAAYAAAAAAABAAAApIFCAAAAdGlueXppcC90aW55LnR4dFVUBQADVsF3WXV4CwABBPUBAAAEFAAAAFBLBQYAAAAAAgACAKQAAACPAAAAAAA=

What I'm getting from Slack seems to be similar to the original... maybe...

UEsDBAoAAAAAAArCkcO5SgAAAAAAAAAAAAAAAAgAHAB0aW55emlwL1VUCQADU8OBd1nCg8OBd1l1eAsAAQTDtQEAAAQUAAAAUEsDBAoAAAAAAAvCkcO5Snp6b8OtAwAAAAMAAAAQABwAdGlueXppcC90aW55LnR4dFVUCQADVsOBd1lcw4F3WXV4CwABBMO1AQAABBQAAABoaQpQSwECHgMKAAAAAAAKwpHDuUoAAAAAAAAAAAAAAAAIABgAAAAAAAAAEADDrUEAAAAAdGlueXppcC9VVAUAA1PDgXdZdXgLAAEEw7UBAAAEFAAAAFBLAQIeAwoAAAAAAAvCkcO5Snp6b8OtAwAAAAMAAAAQABgAAAAAAAEAAADCpMKBQgAAAHRpbnl6aXAvdGlueS50eHRVVAUAA1bDgXdZdXgLAAEEw7UBAAAEFAAAAFBLBQYAAAAAAgACAMKkAAAAwo8AAAAAAA==

Could someone explain what encoding is going on here and how I can correctly upload a file to Slack? Thanks!


Solution

  • How about following sample scripts? There are 2 patterns for this situation.

    Sample script 1 :

    For this, I modified the method you are trying. You can upload the zip file by converting to the byte array as follows. At first, it builds form-data. It adds the zip file converted to byte array and boundary using Buffer.concat(). This is used as body in request.

    var fs = require('fs');
    var request = require('request');
    var upfile = 'sample.zip';
    fs.readFile(upfile, function(err, content){
        if(err){
            console.error(err);
        }
        var token = '### access token ###';
        var filetype = 'zip';
        var filename = 'samplefilename';
        var channel = 'sample';
        var title = 'sampletitle';
        var formString = "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"token\";\n\n" + token;
        formString += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"filetype\";\n\n" + filetype;
        formString += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"filename\";\n\n" + filename;
        formString += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"channels\";\n\n" + channel;
        formString += "\n--abcdefghijklmnop\nContent-Disposition: form-data; name=\"title\";\n\n" + title;
        formString += "\n--abcdefghijklmnop\nContent-Disposition: form-data; filename=\"" + upfile + "\"; name=\"file\";\nContent-Type:application/octet-stream;\n\n";
        var options = {
            method: 'post',
            url: 'https://slack.com/api/files.upload',
            headers: {"Content-Type": "multipart/form-data; boundary=abcdefghijklmnop"},
            body: Buffer.concat([
                Buffer.from(formString, "utf8"),
                new Buffer(content, 'binary'),
                Buffer.from("\n--abcdefghijklmnop\n", "utf8"),
            ]),
        };
        request(options, function(error, response, body) {
            console.log(body);
        });
    });
    

    Sample script 2 :

    This is a simpler way than sample 1. You can use fs.createReadStream() as a file for uploading to Slack.

    var fs = require('fs');
    var request = require('request');
    request.post({
        url: 'https://slack.com/api/files.upload',
        formData: {
            file: fs.createReadStream('sample.zip'),
            token: '### access token ###',
            filetype: 'zip',
            filename: 'samplefilename',
            channels: 'sample',
            title: 'sampletitle',
        },
    }, function(error, response, body) {
        console.log(body);
    });
    

    Result :

    Both sample 1 and sample 2 can be uploaded zip file to Slack as follows. For both, even if filetype is not defined, the uploaded file is used automatically as a zip file.

    enter image description here

    If I misunderstand your question, I'm sorry.