diff --git a/lib/MultipartDataGenerator.js b/lib/MultipartDataGenerator.js index 321820ad73..4225375c40 100644 --- a/lib/MultipartDataGenerator.js +++ b/lib/MultipartDataGenerator.js @@ -1,6 +1,7 @@ 'use strict'; var Buffer = require('safe-buffer').Buffer; +var utils = require('./utils'); // Method for formatting HTTP body for the multipart/form-data specification // Mostly taken from Fermata.js @@ -23,7 +24,7 @@ function multipartDataGenerator(method, data, headers) { return '"' + s.replace(/"|"/g, '%22').replace(/\r\n|\r|\n/g, ' ') + '"'; } - for (var k in data) { + for (var k in utils.flattenAndStringify(data)) { var v = data[k]; push('--' + segno); if (v.hasOwnProperty('data')) { diff --git a/lib/utils.js b/lib/utils.js index 44b378a755..d74ea0f432 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -250,6 +250,41 @@ var utils = module.exports = { // For mocking in tests. _exec: exec, + + isObject: function isObject(obj) { + var type = typeof obj; + return (type === 'function' || type === 'object') && !!obj; + }, + + // For use in multipart requests + flattenAndStringify: function flattenAndStringify(data) { + var result = {}; + + function step(obj, prevKey) { + Object.keys(obj).forEach(function (key) { + var value = obj[key]; + + var newKey = prevKey ? `${prevKey}[${key}]` : key; + + if (utils.isObject(value)) { + if (!Buffer.isBuffer(value) && !value.hasOwnProperty('data')) { + // Non-buffer non-file Objects are recursively flattened + return step(value, newKey); + } else { + // Buffers and file objects are stored without modification + result[newKey] = value; + } + } else { + // Primitives are converted to strings + result[newKey] = String(value); + } + }) + } + + step(data); + + return result; + }, }; function emitWarning(warning) { diff --git a/test/resources/FileUploads.spec.js b/test/resources/FileUploads.spec.js index cf839ca5ee..c8ea6bb6dd 100644 --- a/test/resources/FileUploads.spec.js +++ b/test/resources/FileUploads.spec.js @@ -55,6 +55,7 @@ describe('File Uploads Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }); expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); @@ -73,6 +74,7 @@ describe('File Uploads Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }, TEST_AUTH_KEY); expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); @@ -92,6 +94,7 @@ describe('File Uploads Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }).then(function() { expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); expect(stripe.LAST_REQUEST).to.deep.property('method', 'POST'); @@ -110,6 +113,7 @@ describe('File Uploads Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }, TEST_AUTH_KEY).then(function() { expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); expect(stripe.LAST_REQUEST).to.deep.property('method', 'POST'); diff --git a/test/resources/Files.spec.js b/test/resources/Files.spec.js index 01affd912e..215652cec1 100644 --- a/test/resources/Files.spec.js +++ b/test/resources/Files.spec.js @@ -55,6 +55,7 @@ describe('Files Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }); expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); @@ -73,6 +74,7 @@ describe('Files Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }, TEST_AUTH_KEY); expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); @@ -92,6 +94,7 @@ describe('Files Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }).then(function() { expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); expect(stripe.LAST_REQUEST).to.deep.property('method', 'POST'); @@ -110,6 +113,7 @@ describe('Files Resource', function() { name: 'minimal.pdf', type: 'application/octet-stream', }, + file_link_data: {create: true}, }, TEST_AUTH_KEY).then(function() { expect(stripe.LAST_REQUEST).to.deep.property('host', 'files.stripe.com'); expect(stripe.LAST_REQUEST).to.deep.property('method', 'POST'); diff --git a/test/utils.spec.js b/test/utils.spec.js index 601c39a6a5..f406535825 100644 --- a/test/utils.spec.js +++ b/test/utils.spec.js @@ -4,6 +4,7 @@ require('../testUtils'); var utils = require('../lib/utils'); var expect = require('chai').expect; +var Buffer = require('safe-buffer').Buffer; describe('utils', function() { describe('makeURLInterpolator', function() { @@ -338,6 +339,51 @@ describe('utils', function() { ]); }); }) + + describe('flattenAndStringify', function() { + it('Stringifies primitive types', function() { + expect(utils.flattenAndStringify({ + a: 1, + b: 'foo', + c: true, + d: null, + })).to.eql({'a': '1', 'b': 'foo', 'c': 'true', 'd': 'null'}); + }); + + it('Flattens nested values', function() { + expect(utils.flattenAndStringify({ + x: { + a: 1, + b: 'foo', + }, + })).to.eql({'x[a]': '1', 'x[b]': 'foo'}); + }); + + it('Does not flatten File objects', function() { + expect(utils.flattenAndStringify({ + file: { + data: 'foo' + }, + x: { + a: 1, + }, + })).to.eql({'file': {data: 'foo'}, 'x[a]': '1'}); + }); + + it('Does not flatten Buffer objects', function() { + var buf = Buffer.from('Hi!'); + var flattened = utils.flattenAndStringify({ + buf: buf, + x: { + a: 1, + }, + }); + expect(flattened).to.have.property('buf'); + expect(flattened.buf).to.deep.equal(buf); + expect(flattened).to.have.property('x[a]'); + expect(flattened['x[a]']).to.equal('1'); + }); + }); }); function handleWarnings(doWithShimmedConsoleWarn, onWarn) {