Skip to content

Commit

Permalink
[feat] multipart/form-data support - draft.
Browse files Browse the repository at this point in the history
  • Loading branch information
legander committed Mar 30, 2020
1 parent b98ed39 commit 7b42789
Show file tree
Hide file tree
Showing 11 changed files with 324 additions and 247 deletions.
458 changes: 245 additions & 213 deletions example/full.har

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/make.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ function paramsState () {

function postState () {
return {
species: null
species: null,
boundary: null
}
}

Expand Down
22 changes: 21 additions & 1 deletion src/parse/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,36 @@ function request (node, spec) {
contentType(node.postData.mimeType, spec.headers)
}
state(spec)

if (spec.state.post.boundary) {
addBoundary(spec.state.post.boundary, spec.headers)
}
}

// Fallback to content type from postData
// Preserves explicit header which potentially has more information
function contentType (mimeType, headers) {
if (!headers.has('Content-Type')) {
const item = { value: mimeType }
const items = new Set([ item ])
const items = new Set([item])
headers.set('Content-Type', items)
}
}

function addBoundary (boundary, headers) {
if (headers.has('Content-Type')) {
const items = [...headers.get('Content-Type').values()]
const newItems = items.map(item => {
const value = item.value.split(';')[0]
if (value === 'multipart/form-data') {
return { value: `${value}; boundary=${boundary}` }
}

return item
})

headers.set('Content-Type', new Set(newItems))
}
}

module.exports = request
10 changes: 10 additions & 0 deletions src/parse/state/post.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const { generateBoundary } = require('emailjs-mime-builder/dist/utils')
const { PostSpecies } = require('../../enum')

function post (spec) {
const state = spec.state.post
state.species = species(spec.post)
state.boundary = boundary(spec.post, state.species)
}

function species (spec) {
Expand All @@ -15,4 +17,12 @@ function species (spec) {
}
}

function boundary (spec, species) {
if (species === PostSpecies.Structured && spec.type.split(';')[0] === 'multipart/form-data') {
return generateBoundary(1, Date.now().toString() + Math.random())
} else {
return null
}
}

module.exports = post
22 changes: 14 additions & 8 deletions src/render/post/multipart/fixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ const note = require('../../note/map')
const string = require('../../string')

// Multipart encoded post data without variable
function fixed (params) {
return [
description(params),
value(params)
].filter(item => item).join(`\n`)
function fixed (spec) {
return [description(spec.post.params), value(spec.post.params, spec.state.post.boundary)]
.filter(item => item)
.join(`\n`)
}

function value (params) {
function value (params, boundary) {
const message = new MimeBuilder('multipart/form-data')
for (const [ name, items ] of params) {

if (boundary) {
message.boundary = boundary
}

for (const [name, items] of params) {
for (const item of items) {
message.createChild(item.contentType, { filename: item.fileName })
message
.createChild(item.contentType, { filename: item.fileName })
.setHeader('Content-Disposition', `form-data; name=${name}`)
.setHeader('Content-Transfer-Encoding', item.fileName ? 'base64' : 'quoted-printable')
.setContent(item.value || '')
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/render/post/multipart/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
const fixed = require('./fixed')
const resolved = require('./resolved/arg')
const { UnrecognizedError } = require('../../../error')

function multipart (spec) {
if (spec.state.params.variable) {
// Throw error here until emails-js-builder has runtime support via jslib.
throw new UnrecognizedError(
{ name: 'UnrecognizedStructuredPostType' },
`Unrecognized resolved post data structure MIME type: ${spec.post.type}`
)

// eslint-disable-next-line no-unreachable
return resolved()
} else {
return fixed(spec.post.params)
return fixed(spec)
}
}

Expand Down
1 change: 1 addition & 0 deletions src/render/post/multipart/resolved/pre.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function entry (name, item) {
logic.push('' +
`body.createChild(${args.join(`, `)})
.setHeader("Content-Disposition", ${disposition(name)})
.setHeader("Content-Transfer-Encoding", "${item.fileName ? 'base64' : 'quoted-printable'}")
.setContent(${value(item)});`)
return logic.join(`\n`)
}
Expand Down
9 changes: 4 additions & 5 deletions src/render/post/structured.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// const multipart = require('./multipart')
const multipart = require('./multipart')
const url = require('./url')
const { UnrecognizedError } = require('../../error')

function structured (spec) {
switch (spec.post.type) {
switch (spec.post.type.split(';')[0]) {
case 'application/x-www-form-urlencoded':
return url(spec)
// NOTE: Not supported yet..
// case 'multipart/form-data':
// return multipart(spec)
case 'multipart/form-data':
return multipart(spec)
default:
throw new UnrecognizedError(
{ name: 'UnrecognizedStructuredPostType' },
Expand Down
2 changes: 1 addition & 1 deletion src/validate/postData.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function validate (node, i) {
![
'application/x-www-form-urlencoded',
'multipart/form-data'
].includes(node.mimeType)
].includes(node.mimeType.split(';')[0])
) {
throw new InvalidArchiveError(
{ name: 'InvalidPostDataType' },
Expand Down
20 changes: 10 additions & 10 deletions standalone.js

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions test/unit/render/post/structured.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ test.serial('url', t => {
t.true(multipart.notCalled)
})

// test.serial('multipart', t => {
// const spec = makeRequestSpec()
// spec.post.type = 'multipart/form-data'
// structured(spec)
// t.true(multipart.calledOnce)
// t.true(url.notCalled)
// })
test.serial('multipart', t => {
const spec = makeRequestSpec()
spec.post.type = 'multipart/form-data'
structured(spec)
t.true(multipart.calledOnce)
t.true(url.notCalled)
})

0 comments on commit 7b42789

Please sign in to comment.