http icon indicating copy to clipboard operation
http copied to clipboard

Header for file not correct in multipart request and headers in reverse order?

Open gokkep opened this issue 5 years ago • 6 comments

Reading RFC 6266 and RFC 2388 and looking at my Eiffel web framework server side library code, to handle uploading of files using multipart/form-data it seems something is off somewhere.

It seems that the class MultipartRequest from your library is doing it wrong and I want to find out if this is true.

When a browser sends a file through multipart/form-data, the header and second line of the content type is send as follows:

Content-Disposition: form-data; name="uploaded_file[]"; filename="Apps.png" Content-Type: image/png

But when I upload a file using the http dart library, I receive:

content-type: image/png content-disposition: form-data; name="uploaded_file[]"; filename="Apps.png"

Two things are noticable:

  1. The case of the header, e.g. content-type instead of Content-Type
  2. The two are in reverse order, content-type first should be content-disposition first.

I think this is wrong and should both be modified.

Can someone check if my statements are true and when due, please change them as soon as possible.

Regards, Paul

gokkep avatar Jan 26 '20 16:01 gokkep

Hi @gokkep , i'm facing the same issue. Do you discovered a workaround?

Thx

Xt-Man avatar Feb 05 '20 17:02 Xt-Man

Hi Xt-Man, well no not really. I changed the Eiffel code to support both cases, but it is a hack. This should be solved in the proper way. Shamefull no one of this development team seems to react upon this issue after such long time. Anyone???

gokkep avatar Apr 06 '20 13:04 gokkep

Hi @gokkep , i created my own MultipartRequest, works for me, you can try it, let me now if works for you too. MyMultipartRequest.dart `import 'dart:convert'; import 'dart:math';

import 'package:http/http.dart' as http;

final _newlineRegExp = RegExp(r'\r\n|\r|\n');

const List BOUNDARY_CHARACTERS = [ 43, 95, 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122 ];

class MyMultipartRequest extends http.MultipartRequest {

static const int _boundaryLength = 70;

static final Random _random = Random();

MyMultipartRequest(String method, Uri url) : super(method, url);

@override int get contentLength { var length = 0;

fields.forEach((name, value) {
  length += '--'.length +
      _boundaryLength +
      '\r\n'.length +
      utf8.encode(_headerForField(name, value)).length +
      utf8.encode(value).length +
      '\r\n'.length;
});

for (var file in files) {
  length += '--'.length +
      _boundaryLength +
      '\r\n'.length +
      utf8.encode(_headerForFile(file)).length +
      file.length +
      '\r\n'.length;
}

return length + '--'.length + _boundaryLength + '--\r\n'.length;

}

String _headerForFile(http.MultipartFile file) { // var header = 'content-type: ${file.contentType}\r\n' // 'content-disposition: form-data; name="${_browserEncode(file.field)}"';

// if (file.filename != null) {
//   header = '$header; filename="${_browserEncode(file.filename)}"';
// }

var header = 'Content-Disposition: form-data; name="${_browserEncode(file.field)}"';
if (file.filename != null) {
  header = '$header; filename="${_browserEncode(file.filename)}"';
}
header = '$header\r\n'
  'Content-Type: ${file.contentType}';

return '$header\r\n\r\n';

}

/// Encode [value] in the same way browsers do. String _browserEncode(String value) { // http://tools.ietf.org/html/rfc2388 mandates some complex encodings for // field names and file names, but in practice user agents seem not to // follow this at all. Instead, they URL-encode \r, \n, and \r\n as // \r\n; URL-encode "; and do nothing else (even for % or non-ASCII // characters). We follow their behavior. return value.replaceAll(_newlineRegExp, '%0D%0A').replaceAll('"', '%22'); }

String _headerForField(String name, String value) { var header = 'content-disposition: form-data; name="${_browserEncode(name)}"'; if (!isPlainAscii(value)) { header = '$header\r\n' 'content-type: text/plain; charset=utf-8\r\n' 'content-transfer-encoding: binary'; } return '$header\r\n\r\n'; }

final _asciiOnly = RegExp(r'^[\x00-\x7F]+$');

bool isPlainAscii(String string) => _asciiOnly.hasMatch(string);

@override http.ByteStream finalize() { // TODO: freeze fields and files final boundary = _boundaryString(); headers['content-type'] = 'multipart/form-data; boundary=$boundary'; super.finalize(); return http.ByteStream(_finalize(boundary)); }

Stream<List> _finalize(String boundary) async* { const line = [13, 10]; // \r\n final separator = utf8.encode('--$boundary\r\n'); final close = utf8.encode('--$boundary--\r\n');

for (var field in fields.entries) {
  yield separator;
  yield utf8.encode(_headerForField(field.key, field.value));
  yield utf8.encode(field.value);
  yield line;
}

for (final file in files) {
  yield separator;
  yield utf8.encode(_headerForFile(file));
  yield* file.finalize();
  yield line;
}
yield close;

}

String _boundaryString() { var prefix = 'dart-http-boundary-'; var list = List.generate( _boundaryLength - prefix.length, (index) => BOUNDARY_CHARACTERS[_random.nextInt(BOUNDARY_CHARACTERS.length)], growable: false); return '$prefix${String.fromCharCodes(list)}'; }

}`

Xt-Man avatar Apr 06 '20 13:04 Xt-Man

please use this code its working fine. fileUpload(Request request, String id) async { List dataBytes = [];

await for (var data in request.read()) {
  dataBytes.addAll(data);
}
print(request.headers['content-type']);
var header = HeaderValue.parse(request.headers['content-type'].toString());
var boundary = header.parameters['boundary'] as String;
final transform = MimeMultipartTransformer(boundary);
final bodyStream = Stream.fromIterable([dataBytes]);

final parts = await transform.bind(bodyStream).toList();

print(parts);
for (var part in parts) {
  print(part.headers);
  final contentDisposition = part.headers['content-disposition'];

  final content = await part.toList();
  await File('$id.jpg').writeAsBytes(content[0]);
}

return Response.ok("ok");

}

rajan995 avatar Aug 11 '21 07:08 rajan995

  1. The case of the header, e.g. content-type instead of Content-Type

I filed https://github.com/dart-lang/http/issues/609 as the issue to track support for preserving header case.

2. The two are in reverse order, content-type first should be content-disposition first.

Can you point to where this is specified?

natebosch avatar Aug 17 '21 19:08 natebosch

i'm facing the same issue.

xia-weiyang avatar Sep 18 '22 09:09 xia-weiyang