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

A hook for validating 'multipart/formdata' request (file uploads). #560

Closed
kingdun3284 opened this issue Oct 26, 2019 · 6 comments
Closed

Comments

@kingdun3284
Copy link
Member

As the title said.

@LoicPoullain LoicPoullain added this to Backlog in Issue tracking via automation Oct 30, 2019
@LoicPoullain
Copy link
Member

Thank for submitting this issue @kingdun3284 .

I guess that it would be in the case that we need to upload files? Or do you have also another use case?

@kingdun3284
Copy link
Member Author

Yes, I need to upload file, but not just file and some data field with it. Although it seems ajv can't validate file type and size, I think it should be able to validate those field value. So a parser hook to parse incoming stream and validation hook to validate those fields and emit the remaining file stream to the controller function would be great.

@LoicPoullain
Copy link
Member

LoicPoullain commented Nov 5, 2019

This a definitely a must have. I'm moving this to the To Do column.

In the meantime, if you use @foal/formidable, it's possible to validate the fields manually:

const form = new IncomingForm();
form.uploadDir = 'uploaded';
form.keepExtensions = true;
const { fields, files } = await parseForm(form, ctx);

// Use Ajv to validate `fields`.

This example only addresses one aspect of the problem though.

@LoicPoullain LoicPoullain moved this from Backlog to To Do in Issue tracking Nov 5, 2019
@kingdun3284
Copy link
Member Author

This a definitely a must have. I'm moving this to the To Do column.

In the meantime, if you use @foal/formidable, it's possible to validate the fields manually:

const form = new IncomingForm();
form.uploadDir = 'uploaded';
form.keepExtensions = true;
const { fields, files } = await parseForm(form, ctx);

// Use Ajv to validate `fields`.

This example only addresses one aspect of the problem though.

Yes, I have tried this. And I think it is better not to use formidable, because It has no way to cancel the upload process manually, the file will upload no matter it is valid or not. It is better to use busboy or multar that support the function mention above. So that it won't waste resource and time when the upload file is not valid.

@LoicPoullain LoicPoullain moved this from To Do to Work In Progress in Issue tracking Feb 13, 2020
@LoicPoullain LoicPoullain changed the title Suggestion - a hook for validating 'multipart/formdata' request. A hook for validating 'multipart/formdata' request (file uploads). Feb 13, 2020
@LoicPoullain
Copy link
Member

Overall remarks on multipart/formdata requests

So a parser hook to parse incoming stream and validation hook to validate those fields and emit the remaining file stream to the controller function would be great.

It is technically not possible to create such a hook (that emits remaining file stream to the controller) due to how multipart/formdata requests work. There is no guarantee that the fields reach the server before the file. We cannot make assumptions about the order of arrival of files and fields. We can have, for example, a file, then a field, then another field, then another file.

So there are two solutions IMO:

  • The hook validates the fields and emits buffers to the controller.
  • The hook validates the fields and takes care itself of saving the files to FoalTS filesystem (and removing them if a field is invalidated after the fact).

The below proposal offers the two possibilities (using FoalTS filesystem for the second).

Proposal

Example 1: one field, one file (buffer)

import { ValidateMultipartFormDataBody } from '@foal/storage';

export class ApiController {
  @ValidateMultipartFormDataBody({
    fields: {
      name: { type: 'string' },
    },
    files: [ 'profile' ],
  })
  upload(ctx) {
    ctx.request.body.name // string
    ctx.request.body.profile // Buffer
  }
}

Example 2: multiple files (buffer)

<input type="file" id="profile" name="profile">
<input type="file" id="album" name="album" multiple>
import { ValidateMultipartFormDataBody } from '@foal/storage';

export class ApiController {
  @ValidateMultipartFormDataBody({
    fields: {
      name: { type: 'string' },
    },
    files: [ 'profile', ['album'] ],
  })
  upload(ctx) {
    ctx.request.body.name // string
    ctx.request.body.profile // Buffer
    ctx.request.body.album // Buffer[]
  }
}

Example 3: file saved with FoalTS filesystem (it uses streams under the hood)

import { ValidateMultipartFormDataBody } from '@foal/storage';

export class ApiController {
  @ValidateMultipartFormDataBody({
    fields: {
      name: { type: 'string' },
    },
    files: [ 'profile' ],
    directory: 'avatars/new'
  })
  upload(ctx) {
    ctx.request.body.name // string
    ctx.request.body.profile.path // string. Ex: avatars/new/6xOOeNeytTJLMume3iNzK0PRF68hfh0agLbx2TDqkgg=.png
  }
}

Example 4: file saved with FoalTS filesystem (multiple directories)

import { ValidateMultipartFormDataBody } from '@foal/storage';

export class ApiController {
  @ValidateMultipartFormDataBody({
    fields: {
      name: { type: 'string' },
    },
    files: [ 'profile', [ 'album'] ],
    directory: [ 'avatars', 'album' ]
  })
  upload(ctx) {
    ctx.request.body.name // string
    ctx.request.body.profile.path // string
    ctx.request.body.album[0].path // string
  }
}

@LoicPoullain
Copy link
Member

Version 1.7 published!

Issue tracking automation moved this from Work In Progress to Done / Closed This Release Apr 7, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Issue tracking
  
Done / Closed This Release
Development

No branches or pull requests

2 participants